bzoj4593: [Shoi2015]聚变反应炉
这道题的难点其实是在设DP方程,见过就应该会了
令f0,i表示先激发i的父亲,再激发i,把i的整棵子树都激发的最小费用
f1,i表示先激发i,再激发i的父亲,把i的整棵子树都激发的最小费用
设x,y为i的孩子,先激发x再激发i再激发y有
f0,i=∑(f1,y-cy)+∑f0,x+di-cfa
f1,i=∑(f1,y-cy)+∑f0,x+di
其中差的只有cfa,即0<=f1,i-f0,i<=1,对于typeA就容易做了,若两者相等就选f1,它可以消父亲,否则选f0,因为父亲消的次数有限,而即使消了也只是和选f0得到相同的结果
对于TypeB做树上背包,Fi,j表示第i个点,孩子节点给它贡献了j的最小费用,更新f0,f1
我码力太弱了。。在判断i是否已经被全部消完的时候f0和1要分开判。。。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> using namespace std; struct node { int x,y,next; }a[210000];int len,last[110000]; void ins(int x,int y) { len++; a[len].x=x;a[len].y=y; a[len].next=last[x];last[x]=len; } int n,d[110000],c[110000]; bool type; int f[2][110000],F[2100][11000],li[2100],t[11000]; void dfs(int x,int fr) { if(type==false) { f[0][x]=max(d[x]-c[fr],0); f[1][x]=d[x]; } else F[x][0]=0; int sumc=0; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=fr) { dfs(y,x);sumc+=c[y]; if(type==false) { if(f[0][y]==f[1][y]) { f[0][x]+=f[1][y]-c[y]*((d[x]-c[fr])>=c[y]); f[1][x]+=f[1][y]-c[y]*(d[x]>=c[y]); d[x]-=c[y]; } else f[0][x]+=f[0][y], f[1][x]+=f[0][y]; } else { memset(t,63,sizeof(t)); for(int i=min(sumc,li[x]);i>=0;i--)t[i]=F[x][i]+f[0][y]; for(int i=min(sumc,li[x]);i>=0;i--) { int u=min(sumc,i+c[y]); if(i+c[y]>li[x])t[u]=min(t[u],F[x][i]+f[1][y]); else t[u]=min(F[x][u]+f[0][y],F[x][i]+f[1][y]); } memcpy(F[x],t,sizeof(F[x])); li[x]+=c[y]; } } } if(type==true) { for(int i=min(sumc,li[x]);i>=0;i--) { f[0][x]=min(f[0][x],F[x][i]+max(d[x]-i-c[fr],0)); f[1][x]=min(f[1][x],F[x][i]+max(d[x]-i,0)); } } } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&d[i]); type=false; for(int i=1;i<=n;i++) scanf("%d",&c[i]),type|=(c[i]>1); int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); ins(x,y),ins(y,x); } memset(f,63,sizeof(f)); memset(F,63,sizeof(F)); dfs(1,0); printf("%d\n",f[1][1]); return 0; }
pain and happy in the cruel world.