洛谷 P4269 / loj 2041 [SHOI2015] 聚变反应炉 题解【贪心】【DP】

作者: wjyyy 分类: DP,树形DP,解题报告,贪心 发布时间: 2019-03-04 11:10

点击量:138

树上游戏..二合一?

题目描述

曾经发明了零件组装机的发明家 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$ $ \le 10$ $ c_i = 1$
2 $ =1$ $ \le 100$ $ c_i = 1$
3 $ =1$ $ \le 200$ $ c_i = 1$
4 $ =0$ $ \le 10$
5 $ =1$ $ \le 200$ $ c_i = 1$
6 $ =1$ $ \le 200$
7 $ =1$ $ \le 100000$ $ c_i = 1$
8 $ =0$ $ \le 100000$
9 $ =1$ $ \le 100000$
10 $ =1$ $ \le 100000$
11 $ \le 5$ $ \le 20$
12 $ \le 5$ $ \le 20$ $ c_i$ 均相等
13 $ \le 5$ $ \le 200$
14 $ \le 5$ $ \le 200$ $ c_i$ 均相等
15 $ \le 5$ $ \le 200$
16 $ \le 5$ $ \le 200$
17 $ \le 5$ $ \le 2000$ $ c_i$ 均相等
18 $ \le 5$ $ \le 2000$
19 $ \le 5$ $ \le 2000$
20 $ \le 5$ $ \le 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\{\begin{matrix}
f[i][0]+m&j=0,\\
\min(f[i][j-c_v]-c_v+F[v],f[i][j]+m)&k\le j\le d_i\\
\min_{0\le k\le c_v}\{f[i][d_i-k]-k+F[v]\}&j=d_i
\end{matrix}\right.
$$
其中 $ 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\n",sum);
        return 0;
    }
    dfs(1,1);
    printf("%d\n",F[1]);
    return 0;
}

4
说点什么

avatar
4 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
0 Comment authors
Recent comment authors
  Subscribe  
最新 最旧 得票最多
提醒
trackback

… [Trackback]

[…] Find More on to that Topic: wjyyy.top/3306.html […]

trackback

… [Trackback]

[…] Here you can find 1311 additional Information on that Topic: wjyyy.top/3306.html […]

trackback

… [Trackback]

[…] Find More Information here to that Topic: wjyyy.top/3306.html […]

trackback

… [Trackback]

[…] Read More here to that Topic: wjyyy.top/3306.html […]

/* */