[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;
}
原文地址:https://www.cnblogs.com/qixingzhi/p/9854269.html