[IOI2005]River 河流

题目大意:

给定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]的贡献。不会漏算。

posted @ 2018-05-18 15:27  *Miracle*  阅读(365)  评论(0编辑  收藏  举报