bzoj2427:[HAOI2010]软件安装(Tarjan+tree_dp)
2427: [HAOI2010]软件安装
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1053 Solved: 424
[Submit][Status][Discuss]
Description
现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。
Input
第1行:N, M (0<=N<=100, 0<=M<=500)
第2行:W1, W2, ... Wi, ..., Wn (0<=Wi<=M )
第3行:V1, V2, ..., Vi, ..., Vn (0<=Vi<=1000 )
第4行:D1, D2, ..., Di, ..., Dn (0<=Di<=N, Di≠i )
Output
一个整数,代表最大价值。
Sample Input
3 10
5 5 6
2 3 4
0 1 1
2 3 4
0 1 1
Sample Output
5
HINT
Source
/* 开始一看这不是个基础的树型动规吗?(知道基础,但我不会啊),但是一看还有环嘞,苦逼了... 根据依赖关系可以画出来一张图,有三种可能的情况: 1.依赖关系构成一棵树。 2.依赖关系构成一个环。 3.依赖关系构成一个环下面吊着一棵树。 因为有2,3这些情况,所以要先有tarjan预处理一下,缩环为点,重新建图。 对于建好的图,跑一边树形背包即可,思想类似于01背包 f[x][tot]表示以x为根,容量为tot的最大收益。把x的各个子树看成物品 再枚举每个子树所分给的容量,tot从大到小转移。 */ #include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,m,cnt,scc,ind,top,num; int v[105],w[105]; int sv[105],sw[105];bool in_stack[105]; int dfn[105],low[105],belong[105]; int stack[105],f[105][505],in[505],head[505],head2[505]; struct node { int from; int to; int next; }e[1010],e2[1010]; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void insert(int from,int to) { e[++num].from=from; e[num].to=to; e[num].next=head[from]; head[from]=num; } inline void insert2(int from,int to) { in[to]=1; e2[++num].from=from; e2[num].to=to; e2[num].next=head2[from]; head2[from]=num; } void Tarjan(int u) { int now=0; dfn[u]=low[u]=++ind; stack[++top]=u; in_stack[u]=1; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(!dfn[v]) { Tarjan(v); low[u]=min(low[u],low[v]); } else if(in_stack[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { scc++; while(now!=u)//统计环 { now=stack[top--];in_stack[now]=0; belong[now]=scc; sw[scc]+=w[now]; sv[scc]+=v[now]; } } } void rebuild()//重建图 { num=0; for(int x=1;x<=n;x++) for(int i=head[x];i;i=e[i].next) { int v=e[i].to; if(belong[v]!=belong[x]) insert2(belong[x],belong[v]); } } void dp(int x) { for(int i=head2[x];i;i=e2[i].next) { dp(e2[i].to); for(int j=m-sw[x];j>=0;j--) { for(int k=0;k<=j;k++)//枚举子树的的限制。 f[x][j]=max(f[x][j],f[x][k]+f[e2[i].to][j-k]); } } for(int j=m;j>=0;j--)//完全背包 { if(j>=sw[x])f[x][j]=f[x][j-sw[x]]+sv[x]; else f[x][j]=0; } } int main() { n=read();m=read(); for(int i=1;i<=n;i++)w[i]=read(); for(int i=1;i<=n;i++)v[i]=read(); for(int i=1;i<=n;i++) { int x=read(); if(x)insert(x,i); } for(int i=1;i<=n;i++) if(!dfn[i])Tarjan(i); rebuild(); for(int i=1;i<=scc;i++) if(!in[i]) insert2(scc+1,i);//这个地方要加1,因为根节点属于新的环。(不确定) dp(scc+1); printf("%d\n",f[scc+1][m]); return 0; }
折花枝,恨花枝,准拟花开人共卮,开时人去时。
怕相思,已相思,轮到相思没处辞,眉间露一丝。