题解 P4629 【[SHOI2015]聚变反应炉】

wjyyy

2019-03-04 21:03:04

Solution

## 前言 WC考了个树上游戏三合一,虽然这题是个“二合一”,但也不是非常简单。 **[博客传送门](https://www.wjyyy.top/3306.html)** ## 题解: 前面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: ```cpp #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; } ```