[HAOI2010]软件安装
题目描述
现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为MM计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件ii依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要软件安装了,它就能正常工作。
输入格式
第1行:N,M(0≤N≤100,0≤M≤500)
第2行:W_1,W_2, … W_i, …, W_n (0≤Wi≤M)
第3行:V_1, V_2, …, V_i, …, V_n (0≤Vi≤1000)
第4行:D_1, D_2, …, D_i, …, D_n ( D_i≠i,0≤Di ≤N)
输出格式
一个整数,代表最大价值
这题和选课的最大差别在于这道题可能成环,并且每个物品都有体积和价值两个属性;
Tarjan缩完点后直接树形背包就行了;
dp部分代码有点区别,因为要保证父节点必须被选,所以 j+w[p];
for(int j=m-w[p];j>=0;j--){
for(int k=0;k<=j;k++){
dp[p][j+w[p]]=max(dp[p][j+w[p]],dp[p][j+w[p]-k]+dp[q][k]);
}
}
代码:
#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=11000;
const int M=1000100;
const LL mod=100000000;
int dfn[N],low[N],tot,head[N],cnt,n,m,sta[N],top,fa[N];
int w[N],v[N],d[N],in[N],dp[N][1100];
bool vis[N];
struct Node{
int to,nex;
}edge[M];
void add(int p,int q){
edge[cnt].to=q;
edge[cnt].nex=head[p];
head[p]=cnt++;
}
void Tarjan(int p){
dfn[p]=low[p]=++tot;
if(!vis[p]) vis[p]=true,sta[++top]=p;
for(int i=head[p];~i;i=edge[i].nex){
int q=edge[i].to;
if(!dfn[q]){
Tarjan(q);
low[p]=min(low[p],low[q]);
}
else if(vis[q]) low[p]=min(low[p],dfn[q]);
}
if(dfn[p]==low[p]){
fa[p]=p;
vis[p]=false;
while(sta[top]!=p){
vis[sta[top]]=false;
fa[sta[top]]=p;
w[p]+=w[sta[top]];
v[p]+=v[sta[top]];
top--;
}
top--;
}
}
void dfs(int p){
for(int i=w[p];i<=m;i++) dp[p][i]=v[p];
for(int i=head[p];~i;i=edge[i].nex){
int q=edge[i].to;
dfs(q);
for(int j=m-w[p];j>=0;j--){
for(int k=0;k<=j;k++){
dp[p][j+w[p]]=max(dp[p][j+w[p]],dp[p][j+w[p]-k]+dp[q][k]);
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=n;i++) scanf("%d",&v[i]);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<=n;i++){
if(d[i]) add(d[i],i);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
cnt=0;//重新建边,相当于缩点
memset(head,-1,sizeof(head));
memset(edge,0,sizeof(edge));
for(int i=1;i<=n;i++){
if(d[i]&&fa[i]!=fa[d[i]]){
add(fa[d[i]],fa[i]);
in[fa[i]]++;
}
}
for(int i=1;i<=n;i++) if(!in[fa[i]]&&fa[i]==i) add(0,fa[i]);
dfs(0);
cout<<dp[0][m]<<endl;
return 0;
}