洛谷 P2515 軟體安裝

maniubi發表於2024-09-04

洛谷 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;
}