题目描述
现在我们的手头有 N N N 个软件,对于一个软件 i i i,它要占用 W i W_i Wi 的磁盘空间,它的价值为 V i V_i Vi。我们希望从中选择一些软件安装到一台磁盘容量为 M M M 计算机上,使得这些软件的价值尽可能大(即 V i V_i Vi 的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件 j j j(包括软件 j j j 的直接或间接依赖)的情况下才能正确工作(软件 i i i 依赖软件 j j j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 0 0 0。
我们现在知道了软件之间的依赖关系:软件 i i i 依赖软件 D i D_i Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 D i = 0 D_i=0 Di=0,这时只要这个软件安装了,它就能正常工作。
0 ≤ N ≤ 100 0\le N\le 100 0≤N≤100, 0 ≤ M ≤ 500 0\le M\le 500 0≤M≤500, 0 ≤ W i ≤ M 0\le W_i\le M 0≤Wi≤M, 0 ≤ V i ≤ 1000 0\le V_i\le 1000 0≤Vi≤1000, 0 ≤ D i ≤ N 0\le D_i\le N 0≤Di≤N, D i ≠ i D_i≠i Di̸=i。
算法分析
与金明的预算方案不同的是,这里的依赖关系可能会形成一个环,跑一边 Tarjan 算法缩环,如果我们选择了这个环中的一个节点,那么为了获得价值,该环内的每个点都必选,因此可以看成一个整体,缩环后整个图变成了一棵树,计算树上有依赖背包 DP 即可。
其实可以像 【JSOI 2016】最佳团体 那样进一步缩小枚举范围,但是这题不卡常,就懒得写了。
第一次写的时候居然先清空图再跑了 Tarjan,in[]
数组的必要性在于新图的根节点处可能是一个环。
代码实现
#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn=105;
const int maxm=505;
int head[maxn],ev[maxn],nxt[maxn],idx=0;
inline void add(int u,int v) {ev[++idx]=v;nxt[idx]=head[u];head[u]=idx;}
int dfn[maxn],low[maxn],dfnidx=0,sta[maxn],top=0;
int sccno[maxn],sccidx=0;
void tarjan(int x) {
dfn[x]=low[x]=++dfnidx;sta[top++]=x;
for(int i=head[x];i;i=nxt[i]) {
int v=ev[i];
if(!dfn[v]) {
tarjan(v);
low[x]=std::min(low[x],low[v]);
}
else if(!sccno[v]) low[x]=std::min(low[x],dfn[v]);
}
if(dfn[x]==low[x]) {
++sccidx;
while(top) {
int u=sta[--top];
sccno[u]=sccidx;
if(x==u) break;
}
}
}
int n,m,W[maxn],V[maxn],d[maxn];
int w[maxn],v[maxn],in[maxn],f[maxn][maxm];
void dfs(int x) {
for(int i=0;i<=m;++i) f[x][i]=-0x3f3f3f3f;
f[x][0]=0;if(w[x]<=m) f[x][w[x]]=v[x];
for(int i=head[x];i;i=nxt[i]) {
int v=ev[i];dfs(v);
for(int j=m;j>=0;--j) {
for(int k=0;k<=j-w[x];++k) {
f[x][j]=std::max(f[x][j],f[x][j-k]+f[v][k]);
}
}
}
}
int main() {
scanf("%d%d",&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]);
if(d[i]) add(d[i],i);
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
memset(head,0,sizeof(head));idx=0;sccno[0]=0;
for(int i=1;i<=n;++i) {
w[sccno[i]]+=W[i];v[sccno[i]]+=V[i];
if(d[i]&&sccno[d[i]]!=sccno[i]) {
add(sccno[d[i]],sccno[i]);
++in[sccno[i]];
}
}
for(int i=1;i<=sccidx;++i) if(!in[i]) add(0,i);
dfs(0);
int ans=-0x3f3f3f3f;
for(int i=0;i<=m;++i) ans=std::max(ans,f[0][i]);
printf("%d\n",ans);
return 0;
}