【ZROI】【启发式合并】【17 提高 2】Last mile of the way

做法就是每次将一个点的所有子节点的答案合并到这个点上去,暴力的合并可以每次枚举子节点背包的大小,这样的复杂度是 O(ns2) O ( n s 2 ) 的。可以利用启发式合并的trick,每次将子节点中最重的儿子直接复制过来,然后对于其他子树中的所有节点直接暴力合并,由于合并单个节点的复杂度是 O(s) O ( s ) 的,而每次合并了以后子树大小至少变成了原来的2倍,所以每个节点最多被合并 log2n l o g 2 n 次,这样,复杂度就是 O(nslogn) O ( n s l o g n )

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 5006
#define all 5000
#define LL long long
using namespace std;
int n,T,tot[2],num[maxn],w[maxn],p[maxn],lnk[2][maxn],son[2][maxn*2],nxt[2][maxn*2];
LL f[maxn][maxn];
bool vis[maxn];
void add(int t,int x,int y){
    nxt[t][++tot[t]]=lnk[t][x];son[t][tot[t]]=y;lnk[t][x]=tot[t];
}
void dfs(int x){
    vis[x]=0;num[x]=1;
    for(int j=lnk[0][x];j;j=nxt[0][j]) if(vis[son[0][j]]){
        add(1,x,son[0][j]);dfs(son[0][j]);num[x]+=num[son[0][j]];
    }
}
void merge(int t,int x){
    for(int j=all;j>=w[x];j--)f[t][j]=max(f[t][j],f[t][j-w[x]]+p[x]);
    for(int j=lnk[1][x];j;j=nxt[1][j])merge(t,son[1][j]);
}
void solve(int x){
    int Max=0,t=0;vis[x]=0;
    for(int j=lnk[1][x];j;j=nxt[1][j]){
        if(num[son[1][j]]>Max)Max=num[son[1][j]],t=son[1][j];
        solve(son[1][j]);
    }
    if(t)memcpy(f[x],f[t],sizeof(f[x]));
    for(int j=all;j>=w[x];j--)f[x][j]=max(f[x][j],f[x][j-w[x]]+p[x]);
    for(int j=lnk[1][x];j;j=nxt[1][j]) if(son[1][j]!=t)merge(x,son[1][j]);
}
int main(){
    freopen("A.in","r",stdin);
    freopen("A.out","w",stdout);
    scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),add(0,x,y),add(0,y,x);
    for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&w[i]);
    memset(vis,1,sizeof(vis));
    dfs(1);solve(1);
    scanf("%d",&T);
    while(T--){
        int x,s;
        scanf("%d%d",&x,&s);
        printf("%lld\n",f[x][s]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值