[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\)就不转移,表示并没有这种方案,来保证转移的正确性

猜你喜欢

转载自www.cnblogs.com/ugly-CYW-lyr-ddd/p/11802648.html
今日推荐