zoukankan      html  css  js  c++  java
  • [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)就不转移,表示并没有这种方案,来保证转移的正确性

  • 相关阅读:
    ms4w php配置xdebug
    转载: js 调用父窗口函数-iframe父窗口和子窗口相互的调用方法
    禁止apache列出站内目录
    块元素和行内元素之间的转换,overflow与visibility
    float浮动定位
    绝对定位和固定定位
    相对定位
    边框样式的设置
    div盒子模型
    CSS修饰表格
  • 原文地址:https://www.cnblogs.com/ugly-CYW-lyr-ddd/p/11802648.html
Copyright © 2011-2022 走看看