UVALive4015/LA4015 Cave(树形背包)

题目

n(n<=500)个点的有根树,每条边有边权w(w<=1e4),以下q(q<=1e3)个询问,

每次询问,你需要从根节点出发,走不超过x(x<=5e6)的距离,

访问尽可能多的节点,相同的节点算一个

对于每个查询,输出可以访问的节点数的最大值,出发点也算一个点

思路来源

https://www.cnblogs.com/ympc2005/p/12326550.html

题解

核心代码三四行,剩下都是板子orz

第一维肯定是以u为根的子树,第二维我们关注访问的点的个数,

第二维也考虑过把距离开一维然后dp维护点数,然而距离太大了开不下,所以dp值维护距离

 

转移的时候从u到v,要考虑访问了之后有没有回到根节点u,只有一个dp数组表示不了

所以,开了一个f[i][j]表示以i为根节点,访问了j个节点,并且回到根节点i的最小距离,用于辅助转移

而实际上g[i][j]是最后的答案,因为留在子树里显然更优

g[i][j]表示以i为根节点,访问了j个节点,并且没有回到根节点i的最小距离

 

转移时考虑,

对于g[u][j],可以在v里访问k个回到u,再在之前子树里访问j-k个,

也可以在u之前子树里访问j-k个回到u,再在v里访问k个

对于f[u][j],在v里访问k个回到u,在u之前的子树里访问j-k个回到u,与顺序显然无关

 

考虑背包顺序降维及其转移式的依赖关系,

不能用两次v,故先g后f,且均降序

 

这样复杂度是O(n^3)的,但把背包转移界限严格控制在u和v的子树大小,能让常数更小一点,

听说能控到O(n^2)级别?然而不会证均摊,常数小就vs了

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int,int> P;
#define fi first
#define se second
const int N=505,INF=0x3f3f3f3f;
vector<P>e[N];
int deg[N],sz[N];
//f[i][j]:i的子树回到i的代价
int n,q,rt,x,ca,u,v,w;
int f[N][N],g[N][N];
void dfs(int u){
    f[u][1]=g[u][1]=0;
    sz[u]=1;
    int len=e[u].size();
    for(int i=0;i<len;++i){
        int v=e[u][i].fi,w=e[u][i].se;
        dfs(v);
        sz[u]+=sz[v];
        for(int j=sz[u];j>=2;--j){
            for(int k=1;k<=sz[v];++k){
                if(j-k<0)break;
                g[u][j]=min(g[u][j],f[u][j-k]+w+g[v][k]);
                g[u][j]=min(g[u][j],g[u][j-k]+w+f[v][k]+w);
            }
        }
        for(int j=sz[u];j>=2;--j){
            for(int k=1;k<=sz[v];++k){
                if(j-k<0)break;
                f[u][j]=min(f[u][j],f[u][j-k]+w+f[v][k]+w);
            }
        }
    }
}
int main(){
    while(~scanf("%d",&n) && n){
        memset(f,INF,sizeof f);
        memset(g,INF,sizeof g);
        for(int i=0;i<n;++i){
            deg[i]=sz[i]=0;
            e[i].clear();
        }
        for(int i=1;i<n;++i){
            scanf("%d%d%d",&u,&v,&w);
            e[v].push_back(P(u,w));
            deg[u]++;
        }
        for(int i=0;i<n;++i){
            if(!deg[i]){
                rt=i;
                break;
            }
        }
        dfs(rt);
        scanf("%d",&q);
        printf("Case %d:\n",++ca);
        while(q--){
            scanf("%d",&x);
            for(int i=n;i;--i){
                if(g[rt][i]<=x){
                    //printf("v:%d\n",g[rt][i]);
                    printf("%d\n",i);
                    break;
                }
            }
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值