bzoj4593

感谢王队长对本题的点拨...
    我们先直接以1为根,考虑到一个点的代价貌似只和根与儿子节点有关。所以想到状态f[],h[],分别表示父亲还没炸,炸掉i子树
的花费和父亲先被炸掉的花费,可以观察到h[i]<=f[i]恒成立。
TYPE A :
    因为c[i] <= 1,所以说只存在h[i] = f[i]或者f[i] = h[i] + 1,然后我们考虑如果先炸了i,会给i的父亲节点提供0到1点贡献(可能c[i] = 0,也可能i的父亲已经被炸成0了。)所以当f[i] = h[i],用f[i]向上转移一定是最优的。然后考虑f[i] = h[i] + 1的情况,因为用f[i]不一定能提供一的贡献而h[i]一定能,所以此h[i]更优。所以就可以直接转移了。复杂度o(n),注意父亲节点被儿子节点炸下0后要和0取max。
TYPE B :
    现在c[i] <= 5,先考虑节点u以及它的儿子节点v,如果d[u] > c[v]并且f[v] - h[v] < c[v],这个时候用f[v]转移比h[v]更优,同理当f[v] - h[v] >= c[v]时用h[v]更优,但是这样就完了吗?当然不是,考虑到如果很多节点都选择了f[v]来转移的话,d[u]说不定早就被减成负数了,如果d[u] < 0,此时明显应该是h[v]更优才对,所以先对所有的f[v] - h[v] < c[v]用f[v]转移,如果转移后d[u] < 0,考虑撤销一些f[v],撤销的过程就等于是给u加上c[v]点血从而少花f[v] - h[v]点代价,所以这就是一个背包问题了,在背包的过程可以进行优化,设置一个Limit,Limit等于当前所有用f[v]转移的点的c[v]之和,每新加进来一个点,才把背包上线Limit加上c[v],选取一个最优决策转移就可以了。(注意这里背包做完之后最优解不一定还满足d[u] < 0...).
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
struct node {int to;int next;};node bian[200010];
int first[100010],size,f[100010],h[100010],g[1000010];
int father[100010],d[100010],c[100010],n,a,b,maxc;
void inser(int x,int y) {
	size ++;
	bian[size].to = y;
	bian[size].next = first[x];
	first[x] = size;
}
void dfs(int x,int Anc) {
	father[x] = Anc;
	int _d1 = d[x],_d2 = d[x] - c[father[x]],ret = 0;
	for(int u = first[x];u;u = bian[u].next)
	{
		if(bian[u].to == Anc) continue;
		dfs(bian[u].to,x);
		if(f[bian[u].to] == h[bian[u].to])
		{
			_d1 -= c[bian[u].to];
			_d2 -= c[bian[u].to];
			ret += f[bian[u].to];
		}
		else ret += h[bian[u].to];
	}
	f[x] = ret + max(_d1,0);
	h[x] = ret + max(_d2,0);
}
int getint() {
	char c = 'd';
	int ret = 0;
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9') ret = ret * 10 + c - '0',c = getchar();
	return ret;
}
void bfs(int x,int Anc) {
	father[x] = Anc;
	int _d1 = d[x],_d2 = d[x] - c[father[x]],ret = 0;
	for(int u = first[x];u;u = bian[u].next)
	{
		if(bian[u].to == Anc) continue;
		bfs(bian[u].to,x);
		if(f[bian[u].to] - h[bian[u].to] < c[bian[u].to])
		{
			_d1 -= c[bian[u].to];
			_d2 -= c[bian[u].to];
			ret += f[bian[u].to];
		}
		else ret += h[bian[u].to];
	}
	if(_d1 < 0) 
	{
		int _n = 0;g[0] = 0;
		for(int u = first[x];u;u = bian[u].next)
		{
			if(bian[u].to == Anc) continue;
			if(f[bian[u].to] - h[bian[u].to] >= c[bian[u].to]) continue;
			for(int i = _n + 1;i <= _n + c[bian[u].to];i ++)
				g[i] = 0;
			_n = _n + c[bian[u].to];
			int C = c[bian[u].to],D = f[bian[u].to] - h[bian[u].to];
			for(int i = 0;i + C <= _n;i ++)
				g[i + C] = ((g[i + C] > g[i] + D) ? g[i + C] : g[i] + D);
		}
		f[x] = ret;
		for(int i = 1;i <= _n;i ++)
			f[x] = min(f[x],max(0,_d1 + i) + ret - g[i]);
		//f[x] = ret - g[_n];
	}
	else f[x] = ret + max(_d1,0);
	if(_d2 < 0) 
	{
		int _n = 0;g[0] = 0;
		for(int u = first[x];u;u = bian[u].next)
		{
			if(bian[u].to == Anc) continue;
			if(f[bian[u].to] - h[bian[u].to] >= c[bian[u].to]) continue;
			for(int i = _n + 1;i <= _n + c[bian[u].to];i ++)
				g[i] = 0;
			_n = _n + c[bian[u].to];
			int C = c[bian[u].to],D = f[bian[u].to] - h[bian[u].to];
			for(int i = 0;i + C <= _n;i ++)
				g[i + C] = ((g[i + C] > g[i] + D) ? g[i + C] : g[i] + D);
		}
		h[x] = ret;
		for(int i = 1;i <= _n;i ++)
			h[x] = min(h[x],max(0,_d1 + i) + ret - g[i]);	
	}
	else h[x] = ret + max(_d2,0);
}
int main() {
	n = getint();
	for(int i = 1;i <= n;i ++)
		d[i] = getint();
	for(int i = 1;i <= n;i ++)
		c[i] = getint(),maxc = max(maxc,c[i]);
	for(int i = 2;i <= n;i ++)
	{
		scanf("%d%d",&a,&b);
		inser(a,b);
		inser(b,a);
	}
	if(maxc <= 1) dfs(1,0); else bfs(1,0);
	printf("%d",f[1]);
}


    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值