zoukankan      html  css  js  c++  java
  • 洛谷 P4269 / loj 2041 [SHOI2015] 聚变反应炉 题解【贪心】【DP】

    树上游戏..二合一?

    题目描述

    曾经发明了零件组装机的发明家 SHTSC 又公开了他的新发明:聚变反应炉——一种可以产生大量清洁能量的神秘装置。

    众所周知,利用核聚变产生的能量有两个难点:一是控制核聚变反应的反应强度,二是使用较少的能量激发聚变反应。而 SHTSC 已经完美解决了第一个问题。一个聚变反应炉由若干个相连的聚变块组成,为了能够使得聚变反应可控,SHTSC 保证任意两个聚能块都可以通过相互之间的链接到达,并且没有一个聚能块可以不重复经过一个链接回到它自己。

    但是第二个问题 SHTSC 还没有完全解决。在他设计的聚变反应炉当中,每个聚变块都需要一定的初始能量 (d_i) 来进行激发,不过 SHTSC 不需要手动激发所有聚变块,这是因为一旦一个聚变块被激发,则会向与其直接相连的所有还未被激发的聚变块传送 (c_i) 个单位的能量。这样后被触发的聚变块可以以更低的初始能量来激发,甚至可能不需要额外的外界能量就可自行激发,从而降低了总激发能量的消耗。现在给出了一个聚变反应炉,求至少要多少能量才能激发所有聚变块。

    输入输出格式

    输入格式:

    第一行一个整数 (n),表示共有 (n) 个聚能块,由 (1)(n) 编号。

    第二行 (n) 个整数,依次表示 (d_i)​。

    第三行 (n) 个整数,依次表示 (c_i)​。

    以下 (n-1) 行每行两个整数 (u,v),表示编号为 (u)(v) 的聚能块是相连的。

    输出格式:

    一行一个整数,表示至少需要多少个单位的能量才能激发所有聚变块。

    输入输出样例

    输入样例:

    5
    1 1 1 1 1
    1 1 1 1 1
    1 2
    2 3
    3 4
    4 5
    

    样例输出:

    1
    

    样例解释:

    只需要触发任意一个聚变块即可激活整个聚变反应装置。

    数据范围与约定

    Case # (max{c_i}) (n) 附加限制
    1 (=1) (leq 10) (c_i = 1)
    2 (=1) (leq 100) (c_i = 1)
    3 (=1) (leq 200) (c_i = 1)
    4 (=0) (leq 10) -
    5 (=1) (leq 200) (c_i = 1)
    6 (=1) (leq 200) -
    7 (=1) (leq 100000) (c_i = 1)
    8 (=0) (leq 100000) -
    9 (=1) (leq 100000) -
    10 (=1) (leq 100000) -
    11 (leq 5) (leq 20) -
    12 (leq 5) (leq 20) (c_i) 均相等
    13 (leq 5) (leq 200) -
    14 (leq 5) (leq 200) (c_i) 均相等
    15 (leq 5) (leq 200) -
    16 (leq 5) (leq 200) -
    17 (leq 5) (leq 2000) (c_i) 均相等
    18 (leq 5) (leq 2000) -
    19 (leq 5) (leq 2000) -
    20 (leq 5) (leq 2000) -

    题解:

    前面50分是个贪心。只需要先激发所有的 (1) 再激发所有的 (0) 即可。

    此时考虑 (1) 之间会不会互相影响。因为相邻的 (1) 所造成的影响只是先后顺序上的,早晚都会减掉的,只是位置不同而已。

    后面50分需要高阶树形dp,实则是个背包。用 (f[i][j]) 表示 (i) 号点在已接受儿子们贡献的 (j) 点能量后的最小花费,要把 (j) 当成背包那一维。

    并且有可能出现 (j>d_i) 的情况,但是这是不合法的。因此我们也需要控制,当接收的能量超过 (d_i) 时要按 (d_i) 算。

    此外,对于每个儿子做背包的时候,如果不接受它贡献的能量,则可以自己贡献能量给它。所以dp转移方程并不像以前的背包那样,而是要计算能量下传可能带来的更小代价。

    因此我们做到一个儿子 (v) 的时候,先求出给它下传能量后的最小代价 (m=min{f[v][j]-min(c_i,d_v-j)}),然后dp的时候再利用这个值就可以了。

    因此转移方程为(正在转移儿子 (v)

    [f[i][j]=left{egin{matrix} f[i][0]+m&j=0,\ min(f[i][j-c_v]-c_v+F[v],f[i][j]+m)&kle jle d_i\ min_{0le kle c_v}{f[i][d_i-k]-k+F[v]}&j=d_i end{matrix} ight. ]

    其中 (F[v]=min_{0}^{d[v]}{f[v][i]})

    (F[v]) 的是从儿子获取能量,涉及 (m) 的是自己下传能量。

    不过从儿子获取的能量最多为 (nc_i) ,为10000,因此数组只用开 10000 即可,注意边界问题。

    Code:

    #include<cstdio>
    #include<cstring>
    int Min(int x,int y){return x<y?x:y;}
    struct edge
    {
        int n,nxt;
        edge(int n,int nxt)
        {
            this->n=n;
            this->nxt=nxt;
        }
        edge(){}
    }e[200100];
    int head[100100],ecnt=-1;
    void add(int from,int to)
    {
        e[++ecnt]=edge(to,head[from]);
        head[from]=ecnt;
        e[++ecnt]=edge(from,head[to]);
        head[to]=ecnt;
    }
    int d[100100],c[100100];
    int F[2010];
    void dfs(int x,int from)
    {
        int f[10010];
        memset(f,0x3f,sizeof(f));
        f[x][0]=d[x];
        for(int i=head[x];~i;i=e[i].nxt)
            if(e[i].n!=from)
            {
                dfs(e[i].n,x);
                int k=c[e[i].n],tmp=0x3fffffff,t=F[e[i].n];
    
                for(int j=0;j<=10000;++j)
                {
                    f[x][j]+=t;
                    tmp=Min(tmp,f[e[i].n][j]-Min(c[x],d[e[i].n]-j));
                }
    
                //对于每个物品 拿或不拿都有不同的贡献 需要注意
    
                if(d[x]<=10000)
                {
                    f[x][d[x]]-=t-tmp;
                    for(int j=d[x];j>=d[x]-k;--j)
                        f[x][d[x]]=Min(f[x][d[x]],f[x][j]-(d[x]-j));
                }
    
                for(int j=Min(d[x]-1,10000);j>=k;--j)
                    f[x][j]=Min(f[x][j]-t+tmp,f[x][j-k]-k);
    
                if(k)
                {
                    f[x][0]-=F[e[i].n];//撤销统一修改
                    f[x][0]+=tmp;
                }
    
            }
        for(int i=0;i<=10000;++i)
            if(f[x][i]<F[x])
                F[x]=f[x][i];
    }
    int main()
    {
        memset(f,0x3f,sizeof(f));
        memset(F,0x3f,sizeof(F));
        memset(head,-1,sizeof(head));
        int n,u,v;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&d[i]);
        for(int i=1;i<=n;++i)
            scanf("%d",&c[i]);
        for(int i=1;i<n;++i)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        if(n>2000)//数据分治
        {
            for(int i=1;i<=n;++i)
                if(c[i])
                    for(int j=head[i];~j;j=e[j].nxt)
                        if(e[j].n>i||!c[e[j].n])
                            --d[e[j].n];
            int sum=0;
            for(int i=1;i<=n;++i)
                sum+=d[i]<0?0:d[i];
            printf("%d
    ",sum);
            return 0;
        }
        dfs(1,1);
        printf("%d
    ",F[1]);
        return 0;
    }
    
  • 相关阅读:
    PHP的五大运行模式
    PHP设计模式-工厂模式
    php+redis实现消息队列
    面试题一
    Linux常用命令
    实现session在多台服务器上共享
    Memcache和redis的区别
    PHP设计模式-建造者模式
    FASTCgi和PHP-fpm关系
    安装vm虚拟机
  • 原文地址:https://www.cnblogs.com/wjyyy/p/lg4269.html
Copyright © 2011-2022 走看看