洛谷 P2515 軟體安裝
題意
現在我們的手頭有 \(N\) 個軟體,對於一個軟體 \(i\),它要佔用 \(W_i\) 的磁碟空間,它的價值為 \(V_i\)。我們希望從中選擇一些軟體安裝到一臺磁碟容量為 \(M\) 計算機上,使得這些軟體的價值儘可能大(即 \(V_i\) 的和最大)。
但是現在有個問題:軟體之間存在依賴關係,即軟體 \(i\) 只有在安裝了軟體 \(j\)(包括軟體 \(j\) 的直接或間接依賴)的情況下才能正確工作(軟體 \(i\) 依賴軟體 \(j\))。幸運的是,一個軟體最多依賴另外一個軟體。如果一個軟體不能正常工作,那麼它能夠發揮的作用為 \(0\)。
我們現在知道了軟體之間的依賴關係:軟體 \(i\) 依賴軟體 \(D_i\)。現在請你設計出一種方案,安裝價值儘量大的軟體。一個軟體只能被安裝一次,如果一個軟體沒有依賴則 \(D_i=0\),這時只要這個軟體安裝了,它就能正常工作。
思路
先強連通分量縮點。
如果一個子圖互相依賴,要麼就全裝,要麼就全不裝。
由於原圖每個點入度為 \(1\),是一個外向基環樹森林,縮點後為一個森林。
新增超級源點連向每個根節點,然後跑一遍樹形揹包即可。
程式碼
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m, w[N], v[N];
vector <int> E[N];
int low[N], dfn[N], cnt;
bool instk[N];
int stk[N], top, in[N];
int sc, scc[N], W[N], V[N];
int dp[N][N];
bool e[N][N];
void tarjan(int x) {
low[x] = dfn[x] = ++ cnt;
stk[++ top] = x; instk[x] = 1;
for (auto y : E[x]) {
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
} else if (instk[y])
low[x] = min(low[x], dfn[y]);
}
if (low[x] == dfn[x]) {
sc ++;
while (top && stk[top] != x) {
scc[stk[top]] = sc;
instk[stk[top]] = 0;
W[sc] += w[stk[top]];
V[sc] += v[stk[top]];
top --;
}
scc[stk[top]] = sc;
instk[stk[top]] = 0;
W[sc] += w[stk[top]];
V[sc] += v[stk[top]];
top --;
}
}
void dfs(int x) {
dp[x][W[x]] = V[x];
for (int i = 0; i <= sc; i ++) {
if (!e[x][i]) continue;
dfs(i);
for (int j = m; j >= W[x]; j --)
for (int k = 0; k <= j - W[x]; k ++)
dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[i][k]);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> w[i];
for (int i = 1; i <= n; i ++) cin >> v[i];
for (int i = 1, d; i <= n; i ++) cin >> d, E[d].push_back(i);
for (int i = 1; i <= n; i ++) {
if (dfn[i]) continue;
tarjan(i);
}
for (int i = 1; i <= n; i ++)
for (auto j : E[i])
if (scc[i] != scc[j])
e[scc[i]][scc[j]] = 1,
in[scc[j]] ++;
for (int i = 1; i <= sc; i ++)
if (!in[i]) e[0][i] = 1;
dfs(0);
cout << dp[0][m] << "\n";
return 0;
}