树上游戏…二合一?
题目描述
曾经发明了零件组装机的发明家 SHTSC 又公开了他的新发明:聚变反应炉——一种可以产生大量清洁能量的神秘装置。
众所周知,利用核聚变产生的能量有两个难点:一是控制核聚变反应的反应强度,二是使用较少的能量激发聚变反应。而 SHTSC 已经完美解决了第一个问题。一个聚变反应炉由若干个相连的聚变块组成,为了能够使得聚变反应可控,SHTSC 保证任意两个聚能块都可以通过相互之间的链接到达,并且没有一个聚能块可以不重复经过一个链接回到它自己。
但是第二个问题 SHTSC 还没有完全解决。在他设计的聚变反应炉当中,每个聚变块都需要一定的初始能量 d i d_i di 来进行激发,不过 SHTSC 不需要手动激发所有聚变块,这是因为一旦一个聚变块被激发,则会向与其直接相连的所有还未被激发的聚变块传送 c i c_i ci 个单位的能量。这样后被触发的聚变块可以以更低的初始能量来激发,甚至可能不需要额外的外界能量就可自行激发,从而降低了总激发能量的消耗。现在给出了一个聚变反应炉,求至少要多少能量才能激发所有聚变块。
输入输出格式
输入格式:
第一行一个整数 n n n,表示共有 n n n 个聚能块,由 1 1 1 至 n n n 编号。
第二行 n n n 个整数,依次表示 d i d_i di。
第三行 n n n 个整数,依次表示 c i c_i ci。
以下 n − 1 n-1 n−1 行每行两个整数 u , v u,v u,v,表示编号为 u u u 和 v v 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 } \max\{c_i\} max{ci} n n n 附加限制 1 = 1 =1 =1 ≤ 10 \leq 10 ≤10 c i = 1 c_i = 1 ci=1 2 = 1 =1 =1 ≤ 100 \leq 100 ≤100 c i = 1 c_i = 1 ci=1 3 = 1 =1 =1 ≤ 200 \leq 200 ≤200 c i = 1 c_i = 1 ci=1 4 = 0 =0 =0 ≤ 10 \leq 10 ≤10 - 5 = 1 =1 =1 ≤ 200 \leq 200 ≤200 c i = 1 c_i = 1 ci=1 6 = 1 =1 =1 ≤ 200 \leq 200 ≤200 - 7 = 1 =1 =1 ≤ 100000 \leq 100000 ≤100000 c i = 1 c_i = 1 ci=1 8 = 0 =0 =0 ≤ 100000 \leq 100000 ≤100000 - 9 = 1 =1 =1 ≤ 100000 \leq 100000 ≤100000 - 10 = 1 =1 =1 ≤ 100000 \leq 100000 ≤100000 - 11 ≤ 5 \leq 5 ≤5 ≤ 20 \leq 20 ≤20 - 12 ≤ 5 \leq 5 ≤5 ≤ 20 \leq 20 ≤20 c i c_i ci 均相等 13 ≤ 5 \leq 5 ≤5 ≤ 200 \leq 200 ≤200 - 14 ≤ 5 \leq 5 ≤5 ≤ 200 \leq 200 ≤200 c i c_i ci 均相等 15 ≤ 5 \leq 5 ≤5 ≤ 200 \leq 200 ≤200 - 16 ≤ 5 \leq 5 ≤5 ≤ 200 \leq 200 ≤200 - 17 ≤ 5 \leq 5 ≤5 ≤ 2000 \leq 2000 ≤2000 c i c_i ci 均相等 18 ≤ 5 \leq 5 ≤5 ≤ 2000 \leq 2000 ≤2000 - 19 ≤ 5 \leq 5 ≤5 ≤ 2000 \leq 2000 ≤2000 - 20 ≤ 5 \leq 5 ≤5 ≤ 2000 \leq 2000 ≤2000 -
题解:
前面50分是个贪心。只需要先激发所有的 1 1 1 再激发所有的 0 0 0 即可。
此时考虑 1 1 1 之间会不会互相影响。因为相邻的 1 1 1 所造成的影响只是先后顺序上的,早晚都会减掉的,只是位置不同而已。
后面50分需要高阶树形dp,实则是个背包。用
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示
i
i
i 号点在已接受儿子们贡献的
j
j
j 点能量后的最小花费,要把
j
j
j 当成背包那一维。
并且有可能出现 j > d i j>d_i j>di 的情况,但是这是不合法的。因此我们也需要控制,当接收的能量超过 d i d_i di 时要按 d i d_i di 算。
此外,对于每个儿子做背包的时候,如果不接受它贡献的能量,则可以自己贡献能量给它。所以dp转移方程并不像以前的背包那样,而是要计算能量下传可能带来的更小代价。
因此我们做到一个儿子 v v v 的时候,先求出给它下传能量后的最小代价 m = min { f [ v ] [ j ] − min ( c i , d v − j ) } m=\min\{f[v][j]-\min(c_i,d_v-j)\} m=min{f[v][j]−min(ci,dv−j)},然后dp的时候再利用这个值就可以了。
因此转移方程为(正在转移儿子
v
v
v)
KaTeX parse error: Expected & or \\ or \cr or \end at position 48: …[i][0]+m&j=0,\\\̲ ̲\min(f[i][j-c_v…
其中
F
[
v
]
=
min
0
d
[
v
]
{
f
[
v
]
[
i
]
}
F[v]=\min_{0}^{d[v]}\{f[v][i]\}
F[v]=min0d[v]{f[v][i]}。
加 F [ v ] F[v] F[v] 的是从儿子获取能量,涉及 m m m 的是自己下传能量。
不过从儿子获取的能量最多为 n c i nc_i nci ,为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;
}