[BZOJ5072]小A的树
[BZOJ5072]小A的树
题目描述在pdf里懒得放了
简述下题意:有一个n个点的树,点有颜色:黑和白
有q次询问,每次给定x,y,问是否能选出一个大小为x的联通块,其中恰好包含y个黑点 \(n<=5000,q<=1e5\)
看到\(q=1e5\)感觉回答询问是\(O(1)\)的。
猜个结论,如果对于节点u,选出大小为x的块,最少能包含l个黑点,最多能包含r个黑点,那么l..r也能取到
感性证明一下这个结论:考虑对于u的子节点v往上贡献1个的过程,相当于在v子树中选出的x树中删掉一个点,然后加入u,这样黑点的个数最多会+-1
,所以答案区间是连续的。
由于最终询问的大小为x的联通快 与具体是哪个点为中心无关,所以对于一个大小为x,我们记录它能包含的黑点下限和上限,在询问时查询y是否在这个大区间里即可。在dp过程中即可用每个dp[u][x]
更新L[x]
和R[x]
即可
记\(dp[u][x]\)为u为根的子树中选x个点,最多能选到的黑点个数,f[u][x]
为最少,转移显然是枚举v之前选的点数j,v中选的点数k,\(dp[u][j]+dp[v][t]\)转移到\(dp[u][j+t]\)即可
对于L[x]
和R[x]
,代码中我们用dp[0][x]
和f[0][x]
表示。
#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=x;i<=y;i++)
#define res(i,x,y) for (int i=x;i>=y;i--)
using namespace std;
const int maxn=5010;
struct Edge{
int v,nex;
}edge[maxn<<1];
int f[maxn][maxn],dp[maxn][maxn];
int n,q,head[maxn],siz[maxn],col[maxn],cnt=0;
inline void addEdge(int u,int v){
edge[++cnt]=(Edge){v,head[u]};head[u]=cnt;
}
int read(){
int x=0;char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-48,ch=getchar();
return x;
}
//col=0 白色 col=1黑色
void dfs(int u,int fa){
siz[u]=1;
dp[u][1]=f[u][1]=col[u];
for (int i=head[u];i;i=edge[i].nex){
int v=edge[i].v;
if (v==fa) continue;
dfs(v,u);
res(j,siz[u],0)
res(k,siz[v],0){
dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[v][k]);
f[u][j+k]=min(f[u][j+k],f[u][j]+f[v][k]);
}
siz[u]+=siz[v];
}
rep(i,0,siz[u])
dp[0][i]=max(dp[0][i],dp[u][i]),
f[0][i]=min(f[0][i],f[u][i]);
}
int main(){
int T=read();
while (T--){
memset(dp,0xcf,sizeof(dp));
memset(f,0x3f,sizeof(f));
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
memset(siz,0,sizeof(siz));
cnt=0;
n=read();q=read();
rep(i,1,n-1){
int u=read(),v=read();
addEdge(u,v);addEdge(v,u);
}
rep(i,1,n) col[i]=read();
dfs(1,0);
while (q--){
int x=read(),y=read();
if (f[0][x]<=y && y<=dp[0][x]) puts("YES");
else puts("NO");
}
puts("");
}
//getchar();
return 0;
}
/*
1
9 4
4 1
1 5
1 2
3 2
3 6
6 7
6 8
9 6
0 1 0 1 0 0 1 0 1
3 2
7 3
4 0
9 5
*/
你以为这么简单的就结束了?讲这道题当然不是为了讲这道题
在这题中考虑一个最基础的东西:树形dp的初值(只考虑自底向上的dp
边界条件(形如本代码第23行)我们设为
“当u为叶节点时的答案”
首先当u是叶节点的时候这显然是对的(废话
当有儿子的时候,这时的状态可以认为是,所有儿子都不选,只考虑u这一个点的情况。
对于非边界情况的初值呢?
一定要设置成,比最小的答案还要小(计数题或者取\(max\)
比最大的答案还要大(求\(min\)
啥意思呢
看下这道题的第\(42\)行,\(0xcf\),用\(mem\)赋值出来后\(dp\)整体成为了一个负的
在这之前,我\(memset(dp,0,sizeof(dp))\)成功爆掉
为啥不对?
注意到,可能会有方案使得存在i,j,\(dp[i][j]=0\)成立对吧,如果我初值默认设成0,是不是意味着,对于任意的i,j,都有方案使得\(dp[i][j]\)可能取到0
但是这显然不对啊,事实上有时候0是取不到的,例如u是黑点而且是叶子,则\(dp[u][1]=1\),这个贡献就不对了
换而言之,初值\(dp[i][j]=0意味着\)是一种合法的状态,但是如果事实上并没有一中方案使得他为0呢?这不就贡献了一个错误的答案上去了吗?所以这题的初值必须赋成负数,以保证答案的正确(可以在\(dp\)的时候判断,如果\(dp\)值为负表示没有这个方案,就不转移)
同样,在初值为\(inf\)的\(dp\)中,可以通过判断如果是\(inf\)就不转移,表示并没有这种方案,来保证转移的正确性