題解【bzoj2427 [HAOI2010]軟體安裝】
阿新 • • 發佈:2018-12-02
Description
現在我們的手頭有\(N\)個軟體,對於一個軟體\(i\),它要佔用\(W_i\)的磁碟空間,它的價值為\(V_i\)。我們希望從中選擇一些軟體安裝到一臺磁碟容量為\(M\)計算機上,使得這些軟體的價值儘可能大(即\(V_i\)的和最大)。
但是現在有個問題:軟體之間存在依賴關係,即軟體\(i\)只有在安裝了軟體\(j\)(包括軟體j的直接或間接依賴)的情況下才能正確工作(軟體\(i\)依賴軟體\(j\))。幸運的是,一個軟體最多依賴另外一個軟體。如果一個軟體不能正常工作,那麼它能夠發揮的作用為\(0\)。
我們現在知道了軟體之間的依賴關係:軟體\(i\)依賴軟體\(D_i\)
Solution
明顯是樹形dp。設\(dp[i][j]\)為以\(i\)號點為根的子樹中用不超過\(j\)的空間的最大價值。
但是這道題所給出的條件不能直接構成一棵樹,比如\(d[1]=2,d[2]=3,d[3]=1\)這時\(1,2,3\)便形成一個獨立的聯通塊並且構成環。又由於這個環也很特殊:要麼都選,要麼都不選,所以可以用tarjan將環縮點。新點的\(w=\sum\limits_{e \in \text{該環}}w[e]\),\(v=\sum\limits_{e \in \text{該環}}v[e]\)
縮點後將原來的聯通塊之間的邊連好後再從\(0\)向每一個加完邊後入度為\(0\)的點連一條邊,此時就將原圖轉換為一顆以\(0\)為根的樹,然後就可以愉快的樹形dp辣。
舉個栗子:
如果最開始圖是這樣的
然後縮點,將\(1,2,3\)縮為\(14\),\(10,11,12,13\)縮為\(15\),然後讓\(0\)向幾個聯通塊連邊:
這時原圖被轉換成對答案等價的一棵樹,然後\(dfs\)用上述方程進行簡單的樹上揹包就解決了。
code
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 505; int n, m, cnt, w[MAXN], a[MAXN], d[MAXN]; int dfn[MAXN], low[MAXN], bel[MAXN], tot, scc, ins[MAXN], sta[MAXN], top; int W[MAXN], V[MAXN], indeg[MAXN], dp[MAXN][MAXN]; struct edge { int v; edge *next; }pool[MAXN * 2], *head[MAXN]; inline void addedge(int u, int v) { edge *p = &pool[++cnt]; p->v = v, p->next = head[u], head[u] = p; } void tarjan(int u) { dfn[u] = low[u] = ++tot; sta[++top] = u; ins[u] = 1; for(edge *p = head[u]; p; p = p->next) { int v = p->v; if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(ins[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]) { ++scc; while(sta[top + 1] != u) { bel[sta[top]] = scc; W[scc] += w[sta[top]]; V[scc] += a[sta[top]]; ins[sta[top--]] = 0; } } } void solve(int u) { for(int i = W[u]; i <= m; i++) dp[u][i] = V[u]; for(edge *p = head[u]; p; p = p->next) { int v = p->v; solve(v); int k = m - W[u]; for(int i = k; i >= 0; i--) for(int j = 0; j <= i; j++) dp[u][i + W[u]] = max(dp[u][i + W[u]], dp[v][j] + dp[u][i + W[u] - j]); } } 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", &a[i]); for(int i = 1; i <= n; i++) { scanf("%d", &d[i]); if(d[i]) addedge(d[i], i); } for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i); for(int i = 0; i <= n; i++) head[i] = NULL; cnt = 0; for(int i = 1; i <= n; i++) if(bel[d[i]] != bel[i]) { addedge(bel[d[i]], bel[i]); indeg[bel[i]]++; } for(int i = 1; i <= scc; i++) if(!indeg[i]) addedge(0, i); solve(0); printf("%d\n", dp[0][m]); return 0; }