[HAOI2010] 软件安装

题目类型:\(tarjan\)缩点+树形\(dp\)

传送门:>Here<

题意:给出N各节点,每个节点是一个软件,该软件有占用空间\(W[i]\)和价值\(V[i]\)。软件之间有依赖关系,如果想要运行\(i\),就必须安装\(d[i]\)。问总空间不超过\(M\)时,运行的最大价值

解题思路

首先读题要仔细——安装和运行在这里是两个概念。安装相当于去选择一个软件集合,而在选择完所有的以后才能开始运行。我们可以选择建图,从\(d[i]\)\(i\)连边。通过读题不难发现所有节点的入度都不超过1。因此对于每一个连通块,要么是一个环,要么是一个\(DAG\)。而对于一个环,要么全部安装并运行,要么全部不安装(不可能安装一部分的环)。因此自然而然的,我们可以把环看做一个点,因此首先进行缩点。

缩点完了之后,整张图就变成了一个多个连通块的\(DAG\)。由于不存在环,每个连通块有且仅有存在入度为0的点。从一个虚拟根节点像所有入度为0的点连边,就形成了一个联通的\(DAG\)——由于入度不超过1,可以将其看做一棵树。

于是问题转化为树上的01背包,前提是要选子树的话必须选择该子树的根节点。

考虑树形背包的做法(这一点非常关键)。一般我们都令\(dp[u][j]\)表示以\(u\)为根节点的子树中,总空间设定为\(j\)时的最大价值。然而这样的定义经常使人受到误导——这让人觉得很难转移。

实际上我们定义的原型应该是\(dp[u][i][j]\)表示以\(u\)为根节点的子树中,前\(i\)棵子树中,总空间设定为\(j\)时的最大价值。因此我们有状态转移方程\[dp[u][i][j]=Max\{dp[u][i-1][j],dp[u][i-1][j-k]+dp[v][all][k]\}\]那为什么在普通的定义中没有\(i\)呢?因为\(i\)已经通过滚动数组优化了

这个方程的实质是爆扫——枚举当前子树分配到的空间,剩下的空间给之前的。但是这里有一个特殊的点,就是根节点必须选。这就意味着\(dp[u][i-1][j-k]\)必须包含根节点,其实质也就是\(j-k \geq W[u]\)。这样每次转移的时候不至于让根节点消失了。

反思

读题是非常重要的。不误解题目意思是做出这道题的关键。当然考虑如何必须选根节点来推方程这方面好像还没什么经验,对于\(dp\)还是要多加练习啊。

Code

注意这是一个01背包。由于我们采用滚动数组优化,无论这是不是在树上,外层的\(j\)都是需要倒扫的。(当前一轮取决于上一轮比自己小的,正扫就会覆盖)

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 110;
const int MAXM = 510;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
int N,M,dfs_clock,top;
int w[MAXN],v[MAXN],d[MAXN];
int _first[MAXN],_nxt[MAXM],_to[MAXM],_cnt;
int dfn[MAXN],low[MAXN],sta[MAXN],sccno[MAXN],scc_cnt;
int W[MAXN],V[MAXN],rd[MAXN];
int first[MAXN],nxt[MAXN],to[MAXN],cnt;
int dp[MAXN][50010];
inline void _add(int u, int v){
    _to[++_cnt] = v, _nxt[_cnt] = _first[u], _first[u] = _cnt;
}
inline void add(int u, int v){
    to[++cnt] = v, nxt[cnt] = first[u], first[u] = cnt;
}
void tarjan(int u){
    int _v;
    dfn[u] = low[u] = ++dfs_clock;
    sta[++top] = u;
    for(int i = _first[u]; i; i = _nxt[i]){
        _v = _to[i];
        if(!dfn[_v]){
            tarjan(_v);
            low[u] = Min(low[u], low[_v]);
        }
        else if(!sccno[_v]){
            low[u] = Min(low[u], dfn[_v]);
        }
    }
    if(low[u] == dfn[u]){
        ++scc_cnt;
        while(1){
            sccno[sta[top]] = scc_cnt;
            W[scc_cnt] += w[sta[top]];
            V[scc_cnt] += v[sta[top]];
            --top;
            if(sta[top+1] == u){
                break;
            }
        }
    }
}
void DP(int u){
    for(int j = W[u]; j <= M; ++j){
        dp[u][j] = V[u];
    }
    int v;
    for(int i = first[u]; i; i = nxt[i]){
        v = to[i];
        DP(v);
        for(int j = M; j >= W[u]; --j){
            for(int k = 0; k <= j-W[u]; ++k){
                dp[u][j] = Max(dp[u][j], dp[u][j-k] + dp[v][k]);
            }
        }   
    }
    
}
int main(){
//  freopen(".in","r",stdin);
    N = read(), M = read();
    for(int i = 1; i <= N; ++i) w[i] = read();
    for(int i = 1; i <= N; ++i) v[i] = read();
    for(int i = 1; i <= N; ++i){
        d[i] = read();
        if(d[i]){
            _add(d[i], i);
        }
    }
    for(int i = 1; i <= N; ++i){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    for(int i = 1; i <= N; ++i){
        if(sccno[i] != sccno[d[i]] && d[i]){
            add(sccno[d[i]], sccno[i]);
            ++rd[sccno[i]];
        }
    }
    for(int i = 1; i <= scc_cnt; ++i){
        if(rd[i] == 0){
            add(N+1, i);
        }
    }
    DP(N+1);
    printf("%d", dp[N+1][M]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9854269.html
今日推荐