洛谷

洛谷题单指南-树与图上的动态规划-P2515 [HAOI2010] 软件安装

原题链接:https://www.luogu.com.cn/problem/P2515

题意解读:在一个非联通图中,每个点有体积有价值,求选取不超过m体积的节点的最大价值,选择一个点必须选择其父节点。

解题思路:

由于是非联通图,又由于有可能存在环,而环中任选一个点其他所有点都要选。

因此,可以先用tarjan缩点,缩点的体积、价值是环中所有点的体积、价值之和。

由于不一定连通,建立一个虚拟0点作为根节点,指向所有入度为0的缩点,这样就得到一棵树。

这时,问题就变成了有依赖的背包问题。

1、状态表示

设f[i][j]表示以i为根的子树中总体积不超过j的节点最大价值和。

W[N],V[N]分别是缩点之后的体积、价值

2、状态转移

设根节点u

  先枚举u的所有子树v

    再枚举在u的子树中选择的体积最大值i::m ~ W[u],因为u必选,体积至少是W[u]

      最后枚举在子树v中选择的体积j:0 ~ i - W[u],因为要空出u的体积

        f[u][i] = max(f[u][i], f[v][j] + f[u][i - j]);

3、初始化

for(inti=W[u]; i<=m; i++) f[u][i] =V[u];
4、结果

f[0][m]
100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 105, M = 505;
int w[N], v[N], d[N];
int dfn[N], low[N], timestamp;
stack<int> stk;
bool vis[N];
int scc[N], cnt;
int W[N], V[N];
int SW[N], SV[N], idx;
vector<int> g1[N], g2[N]; //原图,缩点图
int in[N]; //缩点的入度
int f[N][M];
int n, m;

void tarjan(int u)
{
    dfn[u] = low[u] = ++timestamp;
    stk.push(u);
    vis[u] = true;

    for(auto to : g1[u])
    {
        if(!dfn[to])
        {
            tarjan(to);
            low[u] = min(low[u], low[to]);
        }
        else if(vis[to]) low[u] = min(low[u], dfn[to]);
    }

    if(low[u] == dfn[u])
    {
        cnt++;
        while (true)
        {
            int t = stk.top(); stk.pop();
            vis[t] = false;
            scc[t] = cnt;
            W[cnt] += w[t];
            V[cnt] += v[t];
            if(t == u) break;
        }
    }
}

void dfs(int u)
{
    for(int i = W[u]; i <= m; i++) f[u][i] = V[u];

    int sz = 1;
    for(auto v : g2[u])
    {
        dfs(v);

        for(int i = m; i >= W[u]; i--) //以u为根的子树,容量至少是W[u]
            for(int j = 0; j <= i - W[u]; j++) //以v为根的子树,容量不能超过i - w[u]
                f[u][i] = max(f[u][i], f[v][j] + f[u][i - j]);
    }
}

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; i <= n; i++) 
    {
        cin >> d[i];
        if(d[i]) g1[d[i]].push_back(i);
    }

    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i);

    for(int u = 0; u <= n; u++)
    {
        for(auto v : g1[u])
        {
            int a = scc[u], b = scc[v];
            if(a != b) 
            {
                g2[a].push_back(b);
                in[b]++;
            }
        }
    }

    for(int i = 1; i <= cnt; i++) //建立虚拟根节点,指向所有入度为0的节点
        if(in[i] == 0) 
            g2[0].push_back(i);

    dfs(0);
    cout << f[0][m];
    return 0;
}