zoukankan      html  css  js  c++  java
  • [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]的贡献。不会漏算。

  • 相关阅读:
    刷题总结——table(ssoi)
    算法复习——差分约束(ssoi种树)
    刷题总结——谈笑风生(主席树+dfs序的应用)
    刷题总结——序列操作(权值线段树套树状数组)
    刷题总结——shortest(ssoi)
    算法总结——主席树(poj2104)
    刷题总结——路径(ssoi)
    刷题总结——鸭舌(ssoi)
    刷题总结——电影(ssoi)
    刷题总结——随机图(ssoi)
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9056429.html
Copyright © 2011-2022 走看看