COCI 2019/2020 Contest #1 T3「Džumbus」树形背包好题

题面

在这里插入图片描述

样例

1 0
1000
1
1000
0
3 2
1 2 3
1 2
1 3
3
2
3
5
0
2
2
14 13
2 3 4 19 20 21 5 22 6 7 23 8 10 14
1 2
1 3
1 4
2 5
2 6
3 7
3 8
3 9
4 10
8 11
10 13
10 12
12 14
3
45
44
23
8
7
5

分析

神仙题,,难啊,,妙啊,,

起初我在想怎么分配几个子节点的名额,,结果设个 d p 2 dp2 dp2 就完事了。

d p [ x ] [ k ] [ f ] dp[x][k][f] dp[x][k][f] 为根节点为 x x x,名额为 k k k,根节点是否用一个名额 花费的最小饮料。

注意为什么这样设dp,因为若我们把饮料数弄成dp中的元素,数组肯定会炸的。。。

再令 d p 2 [ v ] [ k ] [ f ] dp2[v][k][f] dp2[v][k][f](一个辅助数组) 为根节点为 x x x 的情况下,前 v v v 个子节点,名额为 k k k,根节点是否用一个名额 花费的最小饮料。

易知 d p [ x ] [ k ] [ f ] = d p 2 [ 子 节 点 数 量 ] [ k ] [ f ] dp[x][k][f]=dp2[子节点数量][k][f] dp[x][k][f]=dp2[][k][f]


d p 2 [ v ] [ k ] [ 0 ] = m i n ( d p 2 [ v − 1 ] [ x ] [ 0 ] + m i n ( d p [ n u m [ v ] ] [ k − x ] [ 0 ] , d p [ n u m [ v ] ] [ k − x ] [ 1 ] ) ) dp2[v][k][0]=min(dp2[v-1][x][0]+min(dp[num[v]][k-x][0],dp[num[v]][k-x][1])) dp2[v][k][0]=min(dp2[v1][x][0]+min(dp[num[v]][kx][0],dp[num[v]][kx][1]))

d p 2 [ v ] [ k ] [ 1 ] = m i n dp2[v][k][1]=min dp2[v][k][1]=min

1.    d p 2 [ v − 1 ] [ x ] [ 1 ] + d p [ n u m [ v ] ] [ k − x ] [ 0 ] 1.\ \ dp2[v-1][x][1]+dp[num[v]][k-x][0] 1.  dp2[v1][x][1]+dp[num[v]][kx][0]

2.    d p 2 [ v − 1 ] [ x ] [ 1 ] + d p [ n u m [ v ] ] [ k − x ] [ 1 ] 2.\ \ dp2[v-1][x][1]+dp[num[v]][k-x][1] 2.  dp2[v1][x][1]+dp[num[v]][kx][1]

3.    d p 2 [ v − 1 ] [ x − 1 ] [ 0 ] + d p [ n u m [ v ] ] [ k − x ] [ 1 ] + C o s t [ x ] 3.\ \ dp2[v-1][x-1][0]+dp[num[v]][k-x][1]+Cost[x] 3.  dp2[v1][x1][0]+dp[num[v]][kx][1]+Cost[x]

4.    d p 2 [ v − 1 ] [ x ] [ 0 ] + d p [ n u m [ v ] ] [ k − x − 1 ] [ 0 ] + C o s t [ n u m [ v ] ] + C o s t [ x ] 4.\ \ dp2[v-1][x][0]+dp[num[v]][k-x - 1][0]+Cost[num[v]]+Cost[x] 4.  dp2[v1][x][0]+dp[num[v]][kx1][0]+Cost[num[v]]+Cost[x]

5    d p 2 [ v − 1 ] [ x ] [ 1 ] + d p [ n u m [ v ] ] [ k − x − 1 ] [ 0 ] + C o s t [ n u m [ v ] ] 5\ \ dp2[v-1][x][1]+dp[num[v]][k-x-1][0]+Cost[num[v]] 5  dp2[v1][x][1]+dp[num[v]][kx1][0]+Cost[num[v]]

推这个的时候一定要注意,一不小心就漏掉了。。

注意为什么 3 、 4 3、4 34 点或 2 、 5 2、5 25 点不能合在一起,因为在执行 n u m [ v ] num[v] num[v] 为根节点的时候, d p [ n u m [ v ] ] [ . . . ] [ 1 ] dp[num[v]][...][1] dp[num[v]][...][1] 只会被它的儿子有一个占了名额的更新,也就是说, d p [ n u m [ v ] ] [ . . . ] [ 1 ] dp[num[v]][...][1] dp[num[v]][...][1] 在那时候不会被子节点全不占名额,而它本身要占名额的情况更新。而把 4 、 5 4、5 45 点加上,就使这种情况完美地转移了过来。

粗略算算时间复杂度应该是 O ( n 3 ) O(n^3) O(n3) 的,可存在种种限制比如名额数不能超过这棵树上的总点数,使复杂度趋近于 O ( n 2 ) O(n^2) O(n2)

以下是证明,扒的,有时间再自己看看吧。。

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <climits>
#include <vector>
#define LL long long
using namespace std;
const int MAXN = 1e3 + 5;
const LL lof = 1e18;
int n, m, a[MAXN], C[MAXN];
LL dp[MAXN][MAXN][2], dp2[MAXN][MAXN][2];
int N, Num[MAXN];
LL qwq[MAXN];
vector <int> v[MAXN];
bool vis[MAXN];
void Search(int x) {
	vis[x] = 1; C[x] = 1;
	for(unsigned int i = 0; i < v[x].size(); i ++) {
		int Y = v[x][i]; if(vis[Y]) continue; Search(v[x][i]); C[x] += C[Y];
	}
}
LL Min(LL x, LL y) { return x < y ? x : y; }
int Max(int x, int y) { return x > y ? x : y; }
void dfs(int x, int fa) {
	dp2[0][0][0] = 0; dp2[0][0][1] = lof;
	for(int j = 1; j <= C[x]; j ++) dp2[0][j][0] = dp2[0][j][1] = lof;
	for(unsigned int i = 0; i < v[x].size(); i ++) {
		int Y = v[x][i]; if(Y == fa) continue;
		dfs(Y, x);
	}
	int tot = 1;
	for(unsigned int i = 0; i < v[x].size(); i ++) { // 注意!!:dp值要跑完dfs后更新,因为 dp2是个全局变量,若一边更新一边跑递归会发生紊乱 
		int Y = v[x][i]; if(Y == fa) continue;
		for(int j = 0; j <= C[x]; j ++) {
			dp2[tot][j][0] = dp2[tot][j][1] = lof;
			for(int k = Max(0, j - 1 - C[Y]); k <= j; k ++) {
				dp2[tot][j][0] = Min(dp2[tot][j][0], dp2[tot - 1][k][0] + Min(dp[Y][j - k][0], dp[Y][j - k][1]));
				dp2[tot][j][1] = Min(dp2[tot][j][1], dp2[tot - 1][k][1] + dp[Y][j - k][0]);
				dp2[tot][j][1] = Min(dp2[tot][j][1], dp2[tot - 1][k][1] + dp[Y][j - k][1]);
				if(k >= 1) dp2[tot][j][1] = Min(dp2[tot][j][1], dp2[tot - 1][k - 1][0] + dp[Y][j - k][1] + a[x]);
				if(j >= k + 1 && k >= 1) dp2[tot][j][1] = Min(dp2[tot][j][1], dp2[tot - 1][k - 1][0] + dp[Y][j - k - 1][0] + a[Y] + a[x]);
				if(j >= k + 1) dp2[tot][j][1] = Min(dp2[tot][j][1], dp2[tot - 1][k][1] + dp[Y][j - k - 1][0] + a[Y]);
			}
		}
		tot ++;
	}
	tot --;
	for(int j = 0; j <= C[x]; j ++) dp[x][j][0] = dp2[tot][j][0], dp[x][j][1] = dp2[tot][j][1];
}
int main() {
	int x, y, T;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	for(int i = 1; i <= m; i ++) {
		scanf("%d%d", &x, &y); v[x].push_back(y); v[y].push_back(x);
	}
	for(int i = 1; i <= n; i ++) if(!vis[i]) Search(i), v[0].push_back(i), C[0] += C[i]; // 加个假根节点,方便 dp 
	for(int i = 0; i <= n; i ++) for(int j = 0; j <= C[0]; j ++) dp[i][j][0] = dp[i][j][1] = lof;
	dfs(0, -1);
	for(int i = 0; i <= C[0]; i ++) if(dp[0][i][0] != lof) qwq[++ N] = dp[0][i][0], Num[N] = i;
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &x);
		int t = upper_bound(qwq + 1, qwq + 1 + N, x) - qwq; t --;
		printf("%d\n", Num[t]);
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值