传送门
题目描述
现在我们的手头有 N N N个软件,对于一个软件i,它要占用 W i W_i Wi的磁盘空间,它的价值为 V i V_i Vi。我们希望从中选择一些软件安装到一台磁盘容量为 M M M计算机上,使得这些软件的价值尽可能大(即 V i V_i Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件 i i i只有在安装了软件 j j j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件 i i i依赖软件 j j j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 0 0 0。
我们现在知道了软件之间的依赖关系:软件i依赖软件 D i D_i Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 D i = 0 D_i=0 Di=0,这时只要这个软件安装了,它就能正常工作。
输入格式
第1行:
N
,
M
(
0
≤
N
≤
100
,
0
≤
M
≤
500
)
N,M(0\leq N\leq 100, 0\leq M\leq 500)
N,M(0≤N≤100,0≤M≤500)
第2行: W 1 , W 2 , . . . W i , . . . , W n ( 0 ≤ W i ≤ M ) W_1,W_2, ... W_i, ..., W_n (0\leq W_i\leq M) W1,W2,...Wi,...,Wn(0≤Wi≤M)
第3行: V 1 , V 2 , . . . , V i , . . . , V n ( 0 ≤ V i ≤ 1000 ) V_1, V_2, ..., V_i, ..., V_n (0\leq V_i\leq 1000) V1,V2,...,Vi,...,Vn(0≤Vi≤1000)
第4行: D 1 , D 2 , . . . , D i , . . . , D n ( 0 ≤ D i ≤ N , D i ≠ i ) D_1, D_2, ..., D_i, ..., D_n (0\leq D_i\leq N, D_i≠i) D1,D2,...,Di,...,Dn(0≤Di≤N,Di=i)
输出格式
一个整数,代表最大价值
输入输出样例
输入 #1
3 10
5 5 6
2 3 4
0 1 1
输出 #1
5
解题思路
本题难点
缩点
先进行缩点,然后做DP
缩点(个人理解)
一个有依赖的图中
有一些环,环中点必须全选,只要有一个点不选那么所有点都会失效
比如:1依赖2,2依赖3,3依赖1
不管哪个点不选,整个环都废了
所以可以把环看做一个整体,看做一个点
Tarjan缩点(个人理解)
找出所有的强连通分量(一个环),每个强连通分量缩为一个点,再重新建树
DP
这题DP蛮简单的,就普通的背包
推荐一下某博主的博客,非常详细,就是在那看懂的Tarjan
Code
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct DT{
int to,next;
}sa[3020],a[3020];
int n,m,com,sw[3020],w[3020],sv[3020],v[3020],sd[3020],co[3020],fam[3020],vis[3020];
int snum,shead[3020],num,head[3020],now,top,adfs[3020],afa[3020],hep[3020],dp[3020][5020];//变量看起来好可怕TAT
void sadd(int x,int y){
sa[snum].to=y,sa[snum].next=shead[x],shead[x]=snum++;
}
void add(int x,int y){
a[num].to=y,a[num].next=head[x],head[x]=num++;
}
void tarjan(int x){
now++;
adfs[x]=afa[x]=now;//adfs记录x被发现时间,afa记录x的最早父亲
hep[++top]=x;vis[x]=1;//hep是栈,vis记录x有没有进栈
for(int i=shead[x];i!=-1;i=sa[i].next){
int y=sa[i].to;
if(!adfs[y]){//从没访问过的
tarjan(y);
afa[x]=min(afa[x],afa[y]);
}else if(vis[y])afa[x]=min(afa[x],adfs[y]);
}
if(adfs[x]==afa[x]){//证明已经触底并回溯到根了
++com;//染色(每个强连通分量都被标为一个颜色)
vis[x]=0;
while(hep[top+1]!=x){//退栈
co[hep[top]]=com;//染色
vis[hep[top--]]=0;
}
}
}
void dfs(int x){
for(int i=w[x];i<=m;i++)
dp[x][i]=v[x]; //初始化
for(int k=head[x];k!=-1;k=a[k].next){
int y=a[k].to;
dfs(y);
for(int i=m-w[x];i>=0;i--)
for(int j=0;j<=i;j++)
dp[x][i+w[x]]=max(dp[x][i+w[x]],dp[x][i+w[x]-j]+dp[y][j]);//背包(●—●)
}
}
int main(){
memset(shead,-1,sizeof(shead));
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&sw[i]);
for(int i=1;i<=n;i++)
scanf("%d",&sv[i]);
for(int i=1;i<=n;i++){
scanf("%d",&sd[i]);
if(sd[i])
sadd(sd[i],i);
}
for(int i=1;i<=n;i++)
if(!adfs[i])
tarjan(i);
for(int i=1;i<=n;i++){
v[co[i]]+=sv[i],w[co[i]]+=sw[i];//缩点,把每个强联通分量的价值和费用合并完成缩点
if(co[i]!=co[sd[i]]&&sd[i]){
add(co[sd[i]],co[i]);
fam[co[i]]++;
}
}
int s=com+1;//找一个图外的点作为一个大的根,把森林连成一棵树
for(int i=1;i<=com;i++)
if(!fam[i])
add(s,i);//与根建边
v[s]=w[s]=0;
dfs(s);
printf("%d",dp[s][m+w[s]]);
}