题目
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;
}