BZOJ2427: [HAOI2010]软件安装
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
5 5 6
2 3 4
0 1 1
Sample Output
5
题解Here!
最近沉迷$DP$无法自拔。。。
这是一个有依赖的背包问题。
强行$Tarjan$缩点然后转换成树形$DP$。
缩点应该不用多说。
然后来看树上怎么做。
设$dp[i][j]$表示当前在$i$这个点,用了$j$个单位的磁盘空间,所能获得的最大价值。
转移方程就很好写啦:
设$weight[i]$表示$i$这个点的磁盘空间,$value[i]$表示$i$这个点的价值。
首先对于$DFS$到的所有的$u$,我们初始化:
$$dp[u][i]=value[u],i\in[weight[u],m]$$
对于$\forall v\in son_u$,我们这样转移:
$$dp[u][i+weight[u]]=\max\{\ dp[v][j]+dp[u][i+weight[u]-j]\ |\ i\in[0,m-weight[u]],j\in[0,i]\ \}$$
其中,$i$为倒序枚举,$j$为正序枚举。
这就是树形背包全过程。
但是!缩完点之后有可能不止一棵树啊!
不要紧,我们建立一个超级树根$root$,连到每棵树的树根即可。
最终答案就是$dp[root][m]$。
然而一开始把缩点敲炸了,尴尬。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 110 using namespace std; int n,m,root; int weight[MAXN],value[MAXN],colour[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } namespace Tarjan{//强行namespace,缩点 int c=1,d=1,top=1,s=0; int cstack[MAXN],head[MAXN],deep[MAXN],low[MAXN],w[MAXN],v[MAXN],fa[MAXN]; bool vis[MAXN]; struct Edge{ int next,to; }a[MAXN]; inline void add_edge(int x,int y){ a[c].to=y;a[c].next=head[x];head[x]=c++; } void dfs(int x){ deep[x]=low[x]=d++; vis[x]=true; cstack[top++]=x; for(int i=head[x];i;i=a[i].next){ int v=a[i].to; if(!deep[v]){ dfs(v); low[x]=min(low[x],low[v]); } else if(vis[v])low[x]=min(low[x],deep[v]); } if(low[x]==deep[x]){ s++; do{ colour[cstack[top-1]]=s; vis[cstack[top-1]]=false; }while(cstack[--top]!=x); } } void solve(){ 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++){ fa[i]=read(); if(fa[i])add_edge(fa[i],i); } for(int i=1;i<=n;i++)if(!deep[i])dfs(i); for(int i=1;i<=n;i++){ weight[colour[i]]+=w[i]; value[colour[i]]+=v[i]; } } } namespace DP{//树形背包 int c=1; int head[MAXN],deep[MAXN],indegree[MAXN],dp[MAXN][MAXN*5]; struct Tree{ int next,to; }a[MAXN<<2]; inline void add_edge(int x,int y){ a[c].to=y;a[c].next=head[x];head[x]=c++; } void dfs(int rt){ int will; for(int i=weight[rt];i<=m;i++)dp[rt][i]=value[rt]; for(int i=head[rt];i;i=a[i].next){ will=a[i].to; if(!deep[will]){ deep[will]=deep[rt]+1; dfs(will); for(int j=m-weight[rt];j>=0;j--){ for(int k=0;k<=j;k++){ dp[rt][j+weight[rt]]=max(dp[rt][j+weight[rt]],dp[rt][j+weight[rt]-k]+dp[will][k]); } } } } } void solve(){ root=n+1; weight[root]=value[root]=0; for(int i=1;i<=n;i++){ int v=Tarjan::fa[i]; if(!v)continue; if(colour[i]!=colour[v]){ add_edge(colour[v],colour[i]); indegree[colour[i]]++; } } for(int i=1;i<=Tarjan::s;i++)if(!indegree[i])add_edge(root,i); deep[root]=1; dfs(root); } } void work(){ DP::solve(); printf("%d\n",DP::dp[root][m]); } void init(){ n=read();m=read(); Tarjan::solve(); } int main(){ init(); work(); return 0; }