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(V∗V), 且一共有n个点,故整体的时间复杂度为 o ( V ∗ V ∗ N ) o(V*V*N) o(V∗V∗N).
这个想法也是最容易想的,但是时间复杂度略高.
代码
#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][j−1]+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;
}