1. 程式人生 > >題解【bzoj2427 [HAOI2010]軟體安裝】

題解【bzoj2427 [HAOI2010]軟體安裝】

Description

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

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

我們現在知道了軟體之間的依賴關係:軟體\(i\)依賴軟體\(D_i\)

。現在請你設計出一種方案,安裝價值儘量大的軟體。一個軟體只能被安裝一次,如果一個軟體沒有依賴則\(D_i=0\),這時只要這個軟體安裝了,它就能正常工作。

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