题目描述
现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。
第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 )
输出描述
一个整数,代表最大价值
样例输入
3 10
5 5 6
2 3 4
0 1 1
样例输出
5
思路
很久很久才遇到的一道“可做”的省选题啊。
如果没有环的话,这就是一个简单(指没有什么变化)的树上背包,不过呢,有环。有环也没关系,用tarjan缩点即可(因为tarjan里一个字母写错而调试了半个小时....)。
然后转化成二叉树,左边是孩子右边是兄弟,如果要选择孩子的话就必须选择这个节点,选择兄弟不一定要选择这个节点,我们选一一些兄弟,一些孩子。用一个超级根节点连接所有的没有父节点的节点,这样我们就可以写树上背包的转移方程了:
f[i][j]表示第i个节点占用j的空间的最大价值,我们的状态转移方程是:f[i][j]=max(f[r[i]][j],max(f[l[i]][k]+f[r[i]][j-k-w[i]]+v[i])(0<=k<=j-w[i]));
如果没看懂就看代码吧。
代码
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<climits>
using namespace std;
int n,m,ti=0,sum=0,top=0;
int w[103],v[103],dfn[103],low[103],st[103],color[103],fa[103];
int ww[103],vv[103],f[103][503];
int r[103],l[103],faa[103];
bool in[103],vis[103][503];
vector<int>tree[103];
void tarjan(int now){//tarjan缩点法
int i,j,sz,u;
ti++;dfn[now]=ti;low[now]=ti;
top++;st[top]=now;in[now]=1;
sz=tree[now].size();
for(i=0;i<sz;i++){
u=tree[now][i];
if(dfn[u]==0){
tarjan(u);
low[now]=min(low[now],low[u]);
}
else if(in[u]==1)
low[now]=min(low[now],dfn[u]);
}
if(dfn[now]==low[now]){
sum++;
do{
color[st[top]]=sum;
in[st[top]]=0;
top--;
}while(st[top+1]!=now);
}
}
void dfs(int i,int j){//用dfs实现动态规划
int k;
if(i==0||j==0)return;
if(vis[i][j]==1)return;
vis[i][j]=1;
dfs(r[i],j);
f[i][j]=f[r[i]][j];
for(k=0;k<=j-ww[i];k++){
dfs(l[i],k);dfs(r[i],j-k-ww[i]);
f[i][j]=max(f[i][j],f[l[i]][k]+f[r[i]][j-k-ww[i]]+vv[i]);
}
}
int main()
{
int i,j,sz;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&w[i]);
for(i=1;i<=n;i++)scanf("%d",&v[i]);
for(i=1;i<=n;i++){
scanf("%d",&fa[i]);
if(fa[i]!=0){
tree[i].push_back(fa[i]);
}
}
for(i=1;i<=n;i++)
if(dfn[i]==0)tarjan(i);//tarjan缩点
for(i=1;i<=n;i++){
ww[color[i]]+=w[i];vv[color[i]]+=v[i];//每个缩出来的新点的占用空间和价值
if(fa[i]!=0&&color[fa[i]]!=color[i])faa[color[i]]=color[fa[i]];//找爸爸
}
for(i=1;i<=sum;i++){
if(faa[i]==0)faa[i]=sum+1;//超级根节点连接没有父亲的节点
r[i]=l[faa[i]];//构建左右节点,左节点孩子右节点兄弟
l[faa[i]]=i;
}
color[n+1]=sum+1;
sum++;
dfs(sum,m);
printf("%d",f[sum][m]);
return 0;
}