HAOI 2010 软件安装

4 篇文章 0 订阅

题面

软件安装

题意

一个裸的树形背包问题

解析

缩点

从某个物品依赖的物品向这个物品连边,得到一个有向图。

这时我们会发现,在一个强连通分量里,如果想要有价值的话,必须全部选,根据贪心的思想,对于一个强联通分量,要么全部选,要么全部不选,所以我们可以把这幅有向图进行缩点。

因为每个物品只有一个依赖关系,所以每个强连通分量最多只会有一个父节点,也就是说,缩点后我们得到了森林。

为了方便进行树形背包,我们可以建一个根节点 0 0 0 号节点,把森林转化为一棵树。

背包

状态

树形背包的老套路:
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 i i i 为根的子树,背包容量为 j j j 时的最大价值。

当前节点的状态(体积 v v v),自然是由当前节点较小的背包(体积 v 1 v_1 v1),加上子节点的背包(体积 v 2 v_2 v2)。满足:
v = v 1 + v 2 v = v_1 + v_2 v=v1+v2
定义合法状态:
w [ x ] ≤ v w[x] \leq v w[x]v

阶段

当前节点的背包容量,和子节点的背包容量。

注意外侧当前节点的背包容量采取倒叙循环,避免状态的重复转移。

决策

判断当前阶段是否合法,取最大值。


最后的答案自然就在 d p [ 0 ] [ m ] dp[0][m] dp[0][m] 中了,背包体积越大,自然价值就会相对更大。

小坑点

容易被样例误导,最开始的有向图中可能不会有 0 0 0 号节点的存在,因为物品之间可以互相依赖,所以还是要手动建 0 0 0 号节点。

代码

#include<cstdio>
#include<iostream>
using std:: min;
using std:: max;
const int N = 1e2 + 5,M = 5e2 + 5;
struct edge {
	int next,to;
}a[N];
int head[N],n,m,a_size = 1,wei[N],v[N];
inline void add(int u,int v) {
	a[++a_size] = (edge){head[u],v};
	head[u] = a_size;
}
int dfn[N],low[N],val[N],w[N],c[N],sta[N],top = 0,cnt = 0,num = 0;
bool ins[N];
void tarjan(int x) {
	dfn[x] = low[x] = ++num;
	sta[++top] = x; ins[x] = true;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(!dfn[y]) {
			tarjan(y);
			low[x] =  min(low[x],low[y]); 
		}
		else if(ins[y]) 
			low[x] = min(low[x],dfn[y]);
	}
	if(dfn[x] == low[x]) {
		cnt++; int y;
		do {
			y = sta[top--],ins[y] = false;
			c[y] = cnt; w[cnt] += wei[y]; val[cnt] += v[y];
		}while(x != y);
	}
}
struct New {
	int next,to;
}e[N];
int hc[N],e_size = 1,deg[N];
inline void add_c(int u,int v) {
	e[++e_size] = (New){hc[u],v};
	hc[u] = e_size;
} int dp[N][M];
void dfs(int x) {
	for(int i = w[x]; i <= m; i++)
		dp[x][i] = val[x];
	for(int i = hc[x]; i; i = e[i].next) {
		int y = e[i].to; dfs(y);
		for(int j = m; j >= w[x]; j--)
			for(int k = w[y]; k <= j; k++)
			if(j - k >= w[x]) dp[x][j] = max(dp[x][j],dp[x][j - k] + dp[y][k]);
	}
}
inline int read() {
	int x = 0,flag = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')flag = -1;ch = getchar();}
	while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
	return x * flag;
}
int main() {
	n = read(),m = read();
	for(int i = 1; i <= n; i++)
		wei[i] = read();
	for(int i = 1; i <= n; i++)
		v[i] = read();
	for(int i = 1; i <= n; i++) {
		int u = read();
		if(u) add(u,i);
	}
	for(int i = 1; i <= n; i++)
		if(!dfn[i]) tarjan(i);
	for(int x = 1; x <= n; x++)
		for(int i = head[x]; i; i = a[i].next) {
			int y = a[i].to;
			if(c[x] == c[y]) continue;
			add_c(c[x],c[y]); deg[c[y]]++;
		}
	for(int i = 1; i <= cnt; i++)
		if(!deg[i]) add_c(0,i);
	dfs(0); printf("%d",dp[0][m]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值