题解 P2515 【[HAOI2010]软件安装】

Gypsophila

2018-07-26 18:31:20

Solution

### 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辣。 举个栗子: 如果最开始图是这样的 ![](http://mpic2.lzhaofu.cn/u/14/2018/07/26/e5ec8545.png) 然后缩点,将$1,2,3$缩为$14$,$10,11,12,13$缩为$15$,然后让$0$向几个联通块连边: ![](http://mpic2.lzhaofu.cn/u/14/2018/07/26/f1b84548.png) 这时原图被转换成对答案等价的一棵树,然后$dfs$用上述方程进行简单的树上背包就解决了。 ### code ```cpp #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; } ```