https://vijos.org/p/1770

不重不漏地设计状态才能正确的计数QAQ

虽然可能最优化是正确的,但是不能保证状态不相交就是作死。。。。

之前设的状态错了。。。

应该设

f[i][0]表示i点不取且至少有一个儿子取,且保证i点被覆盖

f[i][1]表示i点取儿子任意,且保证i点被覆盖

f[i][2]表示i点不取且i点的儿子也不取,保证i点不被覆盖!(即留给父亲覆盖)

f[i][2]表示i点不取且儿子也不取。并不是表示i点不取儿子任意!!!!!!!!!!要不然这样会出现交的情况!假设使用后者,那么就会产生和f[i][0]一样的状态!!!!!!

然后我们分别计数

g[i][0]表示f[i][0]的方案数,g[i][1]表示f[i][1]的方案数,g[i][2]表示f[i][2]的方案数,那么有初始化

g[i][0]=0 当i没有儿子时

而转移f[i][1]和f[i][2]很简单,即

f[i][1]=sigma{ min{f[j][0], f[j][1], f[j][2]} (j是i儿子) }

f[i][2]=sigma{ f[j][0] (j是i儿子) }

方案的话加法乘法原理上。。。

f[i][0]转移非常麻烦(因为还要顾及到g[i][0],要做到不重不漏!)

首先我们知道,至少要有一个儿子选中状态才能转移。

如果不小心,很容易得到

f[i][0]=min{f[j][1]+sigma{min{f[k][0], f[k][1]}} (j和k均为i儿子且j!=k) }

这样虽然可以用技巧实现O(n)转移,但是方案却不能够得到!

比如i有2和3这两个儿子,f[3][0]=5, f[3][1]=3, f[2][0]=6, f[2][1]=3;那么转移的时候,两次决策都是f[2][1]+f[3][1]或f[3][1]+f[2][1]!!!!!这样显然不能计数。。重合了。。

所以我之前就这样sb了。。

那么我们考虑如何去重?我们分析得到,我们枚举要取的儿子时,之前枚举过的全部给取f[k][0],没有取过的任意!!!!这样就不会重!

那么问题就好解决了,我们在枚举儿子时,维护一个前缀和f[k][0],k是已经枚举过的,然后再维护一个后缀和,表示sum{min{f[l][0], f[l][1]}},l是未枚举过的

计数的方法相同,那么问题就解决了orz

#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
#define mkpii make_pair<int, int>
#define pdi pair<double, int>
#define mkpdi make_pair<double, int>
#define pli pair<ll, int>
#define mkpli make_pair<ll, int>
#define rep(i, n) for(int i=0; i<(n); ++i)
#define for1(i,a,n) for(int i=(a);i<=(n);++i)
#define for2(i,a,n) for(int i=(a);i<(n);++i)
#define for3(i,a,n) for(int i=(a);i>=(n);--i)
#define for4(i,a,n) for(int i=(a);i>(n);--i)
#define CC(i,a) memset(i,a,sizeof(i))
#define read(a) a=getint()
#define print(a) printf("%d", a)
#define dbg(x) cout << (#x) << " = " << (x) << endl
#define error(x) (!(x)?puts("error"):0)
#define printarr2(a, b, c) for1(_, 1, b) { rep(__, c) cout << a[_][__] << '\t'; cout << endl; }
#define printarr1(a, b) for1(_, 1, b) cout << a[_] << '\t'; cout << endl
inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; }
inline const int max(const int &a, const int &b) { return a>b?a:b; }
inline const int min(const int &a, const int &b) { return a<b?a:b; }
#define rdm(x,i) for(int i=ihead[x]; i; i=e[i].next) const int N=100005, oo=~0u>>1, MD=1000000007;
int ihead[N], cnt, n, f[N][3], l, r[N], st[N];
ll d[N][3], suml, sumr[N];
struct ED { int to, next; }e[N<<1];
ll mul(ll a, ll b) { return ((a%MD)*(b%MD))%MD;}
ll Plus(ll a, ll b) { return ((a%MD)+(b%MD))%MD;}
void add(int u, int v) {
e[++cnt].next=ihead[u]; ihead[u]=cnt; e[cnt].to=v;
e[++cnt].next=ihead[v]; ihead[v]=cnt; e[cnt].to=u;
}
void dfs(int x, int fa) {
int t1=1, t2=0, s1=1, s2=1, y;
rdm(x, i) if((y=e[i].to)!=fa) {
ll tp=0;
dfs(y, x);
int mn=min(min(f[y][0], f[y][1]), f[y][2]); if(f[y][0]==mn) tp+=d[y][0];
if(f[y][1]==mn) tp+=d[y][1];
if(f[y][2]==mn) tp+=d[y][2];
s1=mul(s1, tp); tp=0;
s2=mul(s2, d[y][0]); t1+=mn;
t2+=f[y][0];
}
f[x][1]=t1;
f[x][2]=t2; d[x][1]=s1;
d[x][2]=s2; int sz=0;
rdm(x, i) if(e[i].to!=fa) st[++sz]=e[i].to;
r[sz+1]=0; sumr[sz+1]=1; suml=1; l=0;
for3(i, sz, 1) {
y=st[i];
int mn=min(f[y][0], f[y][1]); ll tp=0;
if(f[y][0]==mn) tp+=d[y][0];
if(f[y][1]==mn) tp+=d[y][1];
r[i]=r[i+1]+mn;
sumr[i]=mul(sumr[i+1], tp);
}
f[x][0]=N;
for1(i, 1, sz) {
if(l+f[st[i]][1]+r[i+1]<f[x][0]) {
f[x][0]=l+f[st[i]][1]+r[i+1];
d[x][0]=mul(d[st[i]][1], mul(suml, sumr[i+1]));
}
else if(l+f[st[i]][1]+r[i+1]==f[x][0]) {
d[x][0]=Plus(d[x][0], mul(d[st[i]][1], mul(suml, sumr[i+1])));
}
if(f[st[i]][0]==N) break;
l+=f[st[i]][0];
suml=mul(suml, d[st[i]][0]);
}
}
int main() {
read(n);
for1(i, 1, n-1) add(getint(), getint());
int root=1;
dfs(root, -1);
int ans1=min(f[root][1], f[root][0]), ans2=0;
if(ans1==f[root][0]) ans2=Plus(ans2, d[root][0]);
if(ans1==f[root][1]) ans2=Plus(ans2, d[root][1]);
printf("%d\n%d\n", ans1, ans2);
return 0;
}

  


背景

大内密探,负责秘密保护皇上,还有保护皇宫内外一切产业。——大内密探零零七

描述

在古老的皇宫中,有N个房间以及N-1条双向通道,每条通道连接着两个不同的房间,所有的房间都能互相到达。皇宫中有许多的宝物,所以需要若干个大内密探来守护。一个房间被守护当切仅当该房间内有一名大内密探或者与该房间直接相邻的房间内有大内密探。

现在身为大内密探零零七的你想知道要把整个皇宫守护好至少需要多少名大内密探以及有多少种安排密探的方案。两种方案不同当且仅当某个房间在一种方案有密探而在另一个方案内没有密探。

格式

输入格式

第一行一个正整数N.(1<=N<=100000)
后面N-1行,每行两个正整数a和b,表示房间a和房间b之间有一条无向通道。

房间的编号从1到N

输出格式

第一行输出正整数K,表示最少安排的大内密探。

第二行输出整数S,表示有多少种方案安排最少的密探,由于结果可能较大,请输出方案数mod 1000000007的余数。

样例1

样例输入1[复制]

 
7
2 1
3 1
4 2
5 1
6 2
7 6

样例输出1[复制]

 
3
4

限制

每个测试点1s

提示

30%保证:n <= 10
70%保证:n <= 1000
100%保证:n <= 100000

【vijos】1770 大内密探(树形dp+计数)的更多相关文章

  1. Vijos p1770 大内密探 树形DP+计数

    4天终于做出来了,没错我就是这么蒟蒻.教训还是很多的. 建议大家以后编树形DP不要用记忆化搜索,回溯转移状态个人感觉更有条理性. 大神题解传送门 by iwtwiioi 我的题解大家可以看注释&quo ...

  2. [vijos 1770]大内密探

    描述 在古老的皇宫中,有N个房间以及N-1条双向通道,每条通道连接着两个不同的房间,所有的房间都能互相到达.皇宫中有许多的宝物,所以需要若干个大内密探来守护.一个房间被守护当切仅当该房间内有一名大内密 ...

  3. Vijos p1518河流 树形DP

    https://vijos.org/p/1518 这题代码我基本是抄的,实在太难想了.但是也学到了一些东西. 比如:多叉树转二叉树存,这个细细一想,确实使得在dfs的时候,实现起来方便很多. 说一说具 ...

  4. vijos 1180 选课 树形DP

    描述 学校实行学分制.每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分.学校开设了N(N<300)门的选修课程,每个学生可选课程的数量M是给定的.学生选修了这M门课并考核通过就能获得 ...

  5. Codeforces Round #277 (Div. 2)D(树形DP计数类)

    D. Valid Sets time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  6. Vijos p1892 树上的最大匹配 树形DP+计数 被卡常我有特殊技巧heheda

    https://vijos.org/p/1892 此题需要手动开栈: <<; //256MB char *p=(char*)malloc(size)+size; __asm__(" ...

  7. 【vijos】1892 树上的最大匹配(树形dp+计数)

    https://vijos.org/p/1892 这个必须得卡评测机+手动开栈才能卡过QAQ 手动开栈我百度的... int size=256<<20; //256MB char *p=( ...

  8. Tree Cutting (Hard Version) CodeForces - 1118F2 (树形DP,计数)

    大意:给定树, 每个点有颜色, 一个合法的边集要满足删除这些边后, 每个连通块内颜色仅有一种, 求所有合法边集的个数 $f[x][0/1]$表示子树$x$中是否还有与$x$连通的颜色 对于每种颜色已经 ...

  9. 牛客第八场 C-counting paths 树形dp计数

    题目地址 题意 给你一颗树 初始点颜色全部为白色 对于每一个满足要求一的点集s f(s)的定义为先把点集内的点染黑 满足要求二的路径集合数量 要求一为两两黑点之间不能出现白色的点 要求二为将这个路径集 ...

随机推荐

  1. PowerDesigner一些常用功能介绍

    主键.自增长等等 修改之前: drop table if exists sys_user; /*==================================================== ...

  2. java中,重构、重载、重写

    1.什么叫重构? 答:重构(Refactoring)就是通过调整程序代码改善软件的质量.性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性,系统发展到一定阶段后,使用重构的方式,不改变系 ...

  3. OpenCV中的矩阵和图像类型

    任务刚刚做完,就迫不及待的来写写在OpenCV中常见的几类数据类型: 在使用OpenCV时我们时常会碰到IplImage这个数据类型,IplImage就是我们通常说的“图像”进行编码的基本结构,这些图 ...

  4. 算法笔记_083:蓝桥杯练习 合并石子(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数.求把所有石子 ...

  5. 常见Style 对象属性值

    Style对象的主要需要关注的属性分为4类,下面分别介绍下: Background 属性 backgroundColor 属性设置元素的背景颜色 Object.style.backgroundColo ...

  6. chromedriver中的浏览器选项

    There are lots of command lines which can be used with the Google Chrome browser. Some change behavi ...

  7. js 创建数组方法以及区别

    示例代码: <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF ...

  8. stderr和stdout详细解说(转)

    今天又查了一下fprintf,其中对第一个参数stderr特别感兴趣. int fprintf(FILE *stream,char *format,[argument]): 在此之前先区分一下:pri ...

  9. iOS 滑块拼图游戏(Puzzle8)

    代码地址如下:http://www.demodashi.com/demo/11505.html 一.准备工作 先了解一个定义和定理 定义:在一个1,2,...,n的排列中,如果一对数的前后位置与大小顺 ...

  10. 分享几款流行的Java框架

    虽然Java一直被唱衰,但是直到现在Java软件开发也坚持霸主地位不动摇,毫无疑问,Java是目前最热门的编程语言之一,下面分享几个个人认为还不错的Java框架,以及各自的优缺点,希望能对大家有帮助. ...