bzoj5072 小A的树 题解

题意

给出一棵 n 个点的树,每个点有黑白两种颜色。q 次询问,每次
询问给出 x 和 y,问能否选出一个 x 个点的联通子图,使得其中
黑点数目为 y。

范围

n ≤ 5000,q ≤ 10^5

其实证明我也不会没弄懂,只是听老师讲了,我们可以猜想:对于某一大小的连通子图,其包含黑点数的最小值与最大值之间的所有点数目都能够取得到。

证明:证明很简单,考虑从最小值一个个删除点并加入点到最大值的过
程,黑点个数每次最多变化 1,因此能遍历从最小值到最大值中
的所有点。(from dzy)

我们定义f[x][y]表示以x为根的树中有y个黑点的最小节点数
同理g[x][y]表示以x为根的树中有y个黑点的最大节点数

然后这题便可以树上背包解决,时间复杂度(n^2)

代码
#include<bits/stdc++.h>
using namespace std;
const int Max=5010;
int n,T,q,tot,u,v,root,xi,yi;
int ver[Max*2],head[Max],Next[Max*2];
int score[Max];
int f[Max][Max],g[Max][Max],siz[Max],ff[Max],gg[Max];
void add(int x,int y){ 
    ver[++tot]=y;Next[tot]=head[x];head[x]=tot;
}
void dp(int x,int fa){//由于存树时用的是双向图,此处要判断 
    siz[x]=1;
    g[x][score[x]]=1;//初始化保证g[x][0] or f[x][0]为1,否则最小值永远是0 
    f[x][score[x]]=1;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(fa==y) continue;
        dp(y,x);
        memcpy(ff,f[x],sizeof f[x]);//由于在进行背包的过程中求得的不一定是最优解,故用临时数组进行储存 
        memcpy(gg,g[x],sizeof g[x]);
        for(int t=siz[x];t>=score[x];--t){//两棵树的合并 
            for(int j=siz[y];j>=score[y];--j){
                ff[t+j]=min(ff[t+j],f[x][t]+f[y][j]);
                gg[t+j]=max(gg[t+j],g[x][t]+g[y][j]);
            }
        }
        siz[x]+=siz[y];
        for(int j=score[x];j<=siz[x];++j){
            f[x][j]=ff[j];
            g[x][j]=gg[j];
        }   
    }
    for(int i=0;i<=siz[x];++i){//用g[0][x] f[0][x]储存答案 
        f[0][i]=min(f[0][i],f[x][i]);
        g[0][i]=max(g[0][i],g[x][i]);
    }
    return;
}
int main(){
    //freopen("trees.in","r",stdin);
    //freopen("trees.out","w",stdout);
    scanf("%d",&T);
    while(T--){
        scanf("%d %d",&n,&q);
        memset(head,0,sizeof(head));
        memset(f,0x3f,sizeof(f));
        memset(g,0xcf,sizeof(g));
        tot=0;
        for(int i=1;i<n;++i){
            scanf("%d %d",&u,&v);
            add(u,v);
            add(v,u);
        }
        for(int i=1;i<=n;++i){
            scanf("%d",&score[i]);
        }
        dp(1,0);
        while(q--){
            scanf("%d %d",&xi,&yi);
            if(xi>=f[0][yi]&&xi<=g[0][yi]){
                puts("YES");
            }
            else puts("NO");
        }printf("\n");
    }
    return 0;
}

注:bzoj需加快读才能过,比较卡时间

转载于:https://www.cnblogs.com/donkey2603089141/p/11414563.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值