【题解】洛谷P2515 & HAOI2010 软件安装
题意
\(N\) 个点,总空间 \(M\) ,每个点占用空间 \(W_i\) ,价值 \(V_i\) 。每个点至多被一个点依赖。
选择一个点,当且仅当该点所有依赖(包括直接和间接)都被选择。求最大价值。
Solution
将依赖转为向被依赖的点连边。可以发现,处在同一个强连通分量里的点要么都选,要么都不选,于是考虑缩点。
缩点之后,考虑树上背包DP,新建一个空间、价值都为 \(0\) 的超级源点,向所有入度为 \(0\) 的点连一条边,由于原先所有点出度均为 \(0\) 或 \(1\) ,那么可以证明:如此操作后,图变为了一课有向树。(感性理解一波)
接着跑树上DP即可。
Code
这次的代码没用四个空格,用了tab,也许会出锅。。。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2 * 110, maxm = 2 * 510;
vector<int> e[maxn], e2[maxn];
int n, m, dfn[maxn], low[maxn], scc[maxn], tot, cnt, rd[maxn];
int f[maxn][maxm]; //f[i][j] 表示以 i 为根子树,剩余空间为 j 的最大价值
struct node
{
int w, v;
} point[maxn];
int w[maxn], v[maxn];
stack<int> stk;
void tarjan(int x)
{
dfn[x] = low[x] = ++cnt;
stk.push(x);
for (int i = 0; i < e[x].size(); i++)
{
int v = e[x][i];
if (!dfn[v])
{
tarjan(v);
low[x] = min(low[x], low[v]);
}
else if (!scc[v])
low[x] = min(low[x], dfn[v]);
}
if (dfn[x] == low[x])
{
tot++;
int tp = 0;
while (tp != x)
{
tp = stk.top();
stk.pop();
scc[tp] = tot;
w[tot] += point[tp].w;
v[tot] += point[tp].v;
}
}
}
void dfs(int x)
{
for (int i = w[x]; i <= m; i++)
{
f[x][i] = v[x];
}
for (int i = 0; i < e2[x].size(); i++)
{
int v = e2[x][i];
dfs(v);
for (int j = m; j >= w[x]; j--) // 枚举剩余空间
for (int k = 0; k <= j - w[x]; k++) // 如果选择这个子树,可选的剩余空间为 0 ~ j - w[x]
f[x][j] = max(f[x][j], f[x][j - k] + f[v][k]);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &point[i].w);
for (int i = 1; i <= n; i++)
scanf("%d", &point[i].v);
int x;
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
if (x != 0)
e[x].push_back(i);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
for (int i = 1; i <= n; i++)
{
for (int j : e[i])
{
if (scc[i] != scc[j])
e2[scc[i]].push_back(scc[j]), rd[scc[j]]++;
}
}
for (int i = 1; i <= tot; i++)
if (!rd[i])
e2[0].push_back(i);
dfs(0);
int ans = 0;
for (int i = 0; i <= m; i++)
ans = max(f[0][m], ans);
cout << ans << endl;
return 0;
}