1. 程式人生 > >【HAOI 2010】軟體安裝

【HAOI 2010】軟體安裝

題目描述

現在我們的手頭有 NN 個軟體,對於一個軟體 ii,它要佔用 WiW_i 的磁碟空間,它的價值為 ViV_i。我們希望從中選擇一些軟體安裝到一臺磁碟容量為 MM 計算機上,使得這些軟體的價值儘可能大(即 ViV_i 的和最大)。

但是現在有個問題:軟體之間存在依賴關係,即軟體i只有在安裝了軟體 jj(包括軟體 jj 的直接或間接依賴)的情況下才能正確工作(軟體 ii 依賴軟體 jj)。幸運的是,一個軟體最多依賴另外一個軟體。如果一個軟體不能正常工作,那麼它能夠發揮的作用為 00

我們現在知道了軟體之間的依賴關係:軟體 ii 依賴軟體 DiD_i。現在請你設計出一種方案,安裝價值儘量大的軟體。一個軟體只能被安裝一次,如果一個軟體沒有依賴則 D

i=0D_i=0,這時只要這個軟體安裝了,它就能正常工作。

0N1000\le N\le 1000M5000\le M\le 5000WiM0\le W_i\le M0Vi10000\le V_i\le 10000DiN0\le D_i\le NDiiD_i≠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; }