题目大意:
给定n个点的有根树,每条边有边权,每个点有点权w,
你要在k个点上建立伐木场,对于每个没有建伐木场的点x,令与它最近的祖先、有伐木场的点,为y,你需要支付dis(x,y)*w[x]的代价。
选择合适的位置建伐木场,最小化总代价。
n<=100
分析:
f[i][j][k]表示, 以i为根的子树中,离其最近的祖先为j,加上这个点的子树共建了k个伐木场。
树形背包,每个点选择建伐木场,或者不选择建。
注意,无论如何,在x子树y回溯后,是可以在子树根节点y造一个伐木场的。这种情况不要漏掉。
因为0处一定有一个伐木场,所以我们先假设它没有这个伐木场,将k++即可。最后答案就是f[0][0][k] 第二维是0,表示0号点最近的伐木场就是它自己,也就是强制让0点建了一个场。
具体做法:
开始f都是inf
进入每一个节点x,都进行初始化。f[x][x][1~k]=0,f[x][x][1]=0,f[x][ancestors][0]=dis*w[x]
进行树形dp的时候,回溯了y儿子,进行背包前,先新建一个数组g,赋值为inf,否则需要f赋值的时候,f却有可能已经变了。
再把g赋值给f。
背包的时候,分类讨论,一个是在x点建场,还有就是不建场,需要枚举一个最近的祖先。这里,我用的是fa[]数组,不断找父亲,以及disf[]到父亲节点的边的长度。
详见代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=100+10; const int K=55; const ll inf=2000000000+1000; struct node{ int nxt,to; ll val; }bian[2*N]; int hd[N],cnt; ll w[N]; int n,k; ll f[N][N][K]; int fa[N]; ll disf[N]; bool vis[N]; void add(int x,int y,ll z) { bian[++cnt].nxt=hd[x]; bian[cnt].to=y; bian[cnt].val=z; hd[x]=cnt; } void dfs(int x) { vis[x]=1; for(int i=1;i<=k;i++) f[x][x][i]=0; ll dis=0;
f[x][x][1]=0; for(int i=x;i!=0;i=fa[i]) { dis+=disf[i]; f[x][fa[i]][0]=dis*w[x]; }// prework for(int i=hd[x];i;i=bian[i].nxt) { int y=bian[i].to; if(!vis[y])//find new son { dfs(y); ll g[N][K]; for(int o=0;o<N;o++) for(int u=0;u<K;u++) g[o][u]=inf; for(int p=1;p<=k;p++)//build one { for(int q=0;q<p;q++) { g[x][p]=min(g[x][p],f[x][x][p-q]+f[y][x][q]);// y's lastest ancestor is x g[x][p]=min(g[x][p],f[x][x][p-q]+f[y][y][q]);// y's lastest ancestor is itself } } for(int j=x;j!=0;j=fa[j])//not { for(int p=0;p<=k;p++) { for(int q=0;q<=p;q++) { g[fa[j]][p]=min(g[fa[j]][p],f[x][fa[j]][p-q]+f[y][fa[j]][q]);//y's lastest ancestor is fa[j] g[fa[j]][p]=min(g[fa[j]][p],f[x][fa[j]][p-q]+f[y][y][q]);// y's lastest ancestor is itself } } } for(int j=x;j!=-1;j=fa[j]) { for(int p=0;p<=k;p++) f[x][j][p]=g[j][p]; }//copy back } } } int main() { scanf("%d%d",&n,&k); k++;//warning!!! int x,y; ll z; for(int i=1;i<=n;i++) { scanf("%lld%d%lld",&w[i],&y,&z); fa[i]=y;disf[i]=z; add(i,y,z); add(y,i,z); } fa[0]=-1; for(int i=0;i<N;i++) for(int j=0;j<N;j++) for(int o=0;o<K;o++) f[i][j][o]=inf; dfs(0); printf("%lld",f[0][0][k]); return 0; }
总结:
1.状态设计值得注意,由于代价和祖先有关,因为这是未来要处理的,不容易直接统计。
所以,就对未来做出承诺,如果在j祖先建伐木场,最小的代价。这里就把这个点运到祖先的代价算上了。
这样,dp到这个祖先的时候,如果决定建造,就可以选择实现承诺。
2.然后,初值都是inf,开始只有f[x][x][1]=0,f[x][ancs][0]=dis*w[x],这就保证了,转移的时候,开始必然有f[x][ancs][0]的贡献。不会漏算。