[HDU - 1561 ] The more, The Better [树上依赖背包的思考]

ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗?
Input
每个测试实例首先包括2个整数,N,M.(1 <= M <= N <= 200);在接下来的N行里,每行包括2个整数,a,b. 在第 i 行,a 代表要攻克第 i 个城堡必须先攻克第 a 个城堡,如果 a = 0 则代表可以直接攻克第 i 个城堡。b 代表第 i 个城堡的宝物数量, b >= 0。当N = 0, M = 0输入结束。
Output
对于每个测试实例,输出一个整数,代表ACboy攻克M个城堡所获得的最多宝物的数量。
Sample Input
3 2
0 1
0 2
0 3
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
0 0
Sample Output
5
13
题意: 就是裸的树上依赖背包.
做这题之前可以把背包九讲再复习一遍.

思考一:使用泛化背包的思想
这里我们可以将每个点当做一个泛化物品,然后自下向上进行泛化物品合并,因为任意两个泛化物品进行合并的时间复杂度为 o ( V ∗ V ) o(V*V) o(VV), 且一共有n个点,故整体的时间复杂度为 o ( V ∗ V ∗ N ) o(V*V*N) o(VVN).
这个想法也是最容易想的,但是时间复杂度略高.
代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define rep(i, l, r) for(int i = l; i < r; i++)
#define per(i, r, l) for(int i = r; i >= l; i--)
#define dbg(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"<<"\n"

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;

const int N = (int) 500 + 11;
const int M = (int) 1000 + 11;
const int MOD = (int) 1e9 + 7;
const int INF = (int) 0x3f3f3f3f;
const ll INFF = (ll) 0x3f3f3f3f3f3f3f3f;
/*-----------------------------------------------------------*/
int n, m;
vector<int>ve[N];
int dp[N][N], val[N];
void dfs(int rt){
	for(int i = 0; i < ve[rt].size(); i++){
		int v = ve[rt][i];
		dfs(v);
		for(int j = m; j >= 1; j--){ // 两个泛化物品的合并,总是合并到根节点上
			for(int k = j - 1; k >= 0; k--){
				dp[rt][j] = max(dp[rt][j], dp[rt][j - k] + dp[v][k]);
			}
		}
	}
}
int main(){
	while(scanf("%d%d", &n, &m) && (n || m)){
		int rt = 0; m++; 
		rep(i, 0, n  + 2) ve[i].clear();
		memset(dp, 0, sizeof(dp));
		rep(i, 1, n + 1){
			static int a, b; scanf("%d%d", &a, &b);
			ve[a].push_back(i);	
			val[i] = b;
			dp[i][1] = b;
		}
		dfs(rt);
		printf("%d\n", dp[rt][m]);
	}
	return 0;
}

思考二:网上有个用dfs序的巧妙方法, d p [ i ] [ j ] dp[i][j] dp[i][j]表示第i个点及以后的dfs序大小为j的联通块的最大权值.
然后就可以得到递推方程:
x: dfs序为i所对应的点
d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j − 1 ] + v a l [ x ] , d p [ i + s z [ x ] ] [ j ] ) dp[i][j] = max(dp[i + 1][j - 1] + val[x] , dp[i + sz[x] ][j] ) dp[i][j]=max(dp[i+1][j1]+val[x],dp[i+sz[x]][j])

代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define rep(i, l, r) for (int i = l; i < r; i++)
#define per(i, r, l) for (int i = r; i >= l; i--)
#define dbg(...)                                                               \
	cerr << "[" << #__VA_ARGS__ ":" << (__VA_ARGS__) << "]"                      \
<< "\n"

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const int N = (int)1000 + 11;
const int M = (int)1000 + 11;
const int MOD = (int)1e9 + 7;
const int INF = (int)0x3f3f3f3f;
const ll INFF = (ll)0x3f3f3f3f3f3f3f3f;
/*-----------------------------------------------------------*/

vector<int> ve[N];
int in[N], out[N], sz[N], rid[N];
int val[N];
int clo;
void dfs(int u) {
	in[u] = ++clo;
	rid[clo] = u;
	sz[u] = 1;
	for (auto &v : ve[u]) {
		dfs(v);
		sz[u] += sz[v];
	}
	out[u] = clo;
}
int dp[N][M];
int main() {
	int n, m;
	while (cin >> n >> m) {
		if(n == 0 && n == m) break;
		memset(val, 0, sizeof(val));
		for (int i = 0; i <= n; i++)
			ve[i].clear();
		rep(i, 1, n + 1) {
			static int a, b;
			cin >> a >> b;
			ve[a].push_back(i);
			val[i] = b;
		}
		clo = 0;
		dfs(0); // 处理dfs序
		//		rep(i, 0, n + 1) { cout << i << " " << in[i] << " " <<
		// out[i] << " " << sz[i] << "\n";  }
		memset(dp, 0, sizeof(dp));
		for(int i = 2; i <= 2*m; i++) dp[clo][i] = -INF; dp[clo][1] = val[rid[clo]];  // 初始化
		m++;
		for (int i = clo - 1; i > 0; i--) {
			int x = rid[i];
			dp[i][1] = max(val[x], dp[i + sz[x]][1]);
			for (int j = 2; j <= m; j++) {
				dp[i][j] = max(dp[i + sz[x]][j], dp[i + 1][j - 1] + val[x]); 	
//				cout << i << " " << j << " " << dp[i][j] << "\n";
			}
//			dbg(dp[i][1]);
		}
		cout << dp[1][m] << "\n";
	}
	return 0;
}

思考:抽象为一个模板:
问题:给一个森林,每个点都有重量和价值,且要想买一个节点,必须选买过其父亲节点,问现在有一个容量为V的背包,可以获得的最大价值.(我们建立一个超级源点,这样就转化为了一个颗树的问题了)
代码

/***********************************************
Author        :lzs
Created Time  :2018年10月09日 星期二 19时32分05秒
File Name     :yy.cpp
 ************************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define rep(i, l, r) for(int i = l; i < r; i++)
#define per(i, r, l) for(int i = r; i >= l; i--)
#define dbg(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"<<"\n"


typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;

const int N = (int) 1000 + 11;
const int M = (int) 1000 + 11;
const int MOD = (int) 1e9 + 7;
const int INF = (int) 0x3f3f3f3f;
const ll INFF = (ll) 0x3f3f3f3f3f3f3f3f;
/*-----------------------------------------------------------*/

vector <int>ve[N];
int c[N], w[N];
int dp[N][M];
int in[N], sz[N], rid[N], clo;
void dfs(int u){
	in[u] = ++clo; sz[u] = 1; rid[clo] = u;
	for(auto &v : ve[u]){
		dfs(v);
		sz[u] += sz[v];
	}
}
int main(){
	int n, V;
	while(cin >> n >> V){
		if(n == 0 && V == 0) break;
		rep(i, 0, n + 1) ve[i].clear();
		rep(i, 1, n + 1) {
			static int a, b; cin >> a >> b;
			ve[a].push_back(i);
			w[i] = b;
			c[i] = 1;
		}	
		w[0] = 0, c[0] = 1; 
		
		int rt = 0;  // 0 是超级源点
		clo = 0; dfs(rt);
		int x = rid[clo];
		for(int i = 1; i <= V + 10; i++){
			if(i == c[x]) dp[clo][i] = w[x];
			else dp[clo][i] = -INF;
		} 
		V++;
		for(int i = clo - 1; i > 0; i--){
			int x = rid[i];
			for(int j = 1; j <= V; j++){
				if(j <= c[x]) dp[i][j] = max(0, max(w[x], dp[i + sz[x]][j]));
				else dp[i][j] = max(0, max(dp[i + sz[x]][j], dp[i + 1][j - c[x]] + w[x]));
			}
		}	
		cout << dp[1][V] <<"\n";
	}	
	return 0;
}

你可能感兴趣的:(背包问题,树形DP,结构的模版)