Codeforce 1099 F. Cookies(树形DP + 线段树预处理)

16 篇文章 0 订阅
14 篇文章 0 订阅

在这里插入图片描述
题目大意:在一棵有根树上,每个点有 x[i] 个饼干,在第 i 个点吃饼每块饼干要花 t[i] 的时间。
现在有两个玩家 a,b,总共有T时间。a 初始在根节点,每次可以沿着树边走向子结点,每次移动都要花费一定的时间,a也可以结束游戏 ,当他结束游戏时,他开始向根结点返回,在返回途中他可以吃饼,但最后必须回到根节点。当 a 移动到 结点 i 时,b 可以选择删掉从 i 出发到其子节点的一条边。
a 先手,b 后手 ,求无论 b 如何操作,a 能吃到的最大饼数。


题解:假设 a 当前走到了 i,想要结束游戏,我们要算出在剩余时间内从 i 返回到根节点的最大吃饼数,显然可以贪心,将这条路径上的饼按吃饼需要的时间排序,优先吃需要时间最少的。这可以用权值线段树维护,在线段树上二分。

用线段树按 dfs的顺序预处理出每一个点回到根节点能吃的最大饼数,记为 v[i]。问题就可以转化为从根节点向下走,在 b 的干扰下能走到的v[i] 最大的点是哪一个点。
由于 a 先手,除了 a 在根节点处是先走 外,其它点都是 b 先删掉一条边,a 再走。

不难发现 从i 的子节点向下走是从 i 向下走的一个子问题,可以树形DP。
假设当前从 i 点出发,已经处理出所有 i 的子节点出发能走到的最大权值 v,如果这时选择向下走,b 肯定会删掉一条答案最大的边,a 只能走答案次大的边,再用这个结果和直接从 i 点返回比较,取较大值。由于 a 先手,在根节点时可以沿着答案最大的边往下走,特判一下即可。


(预处理可以上树上主席树,但没必要)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lson rt << 1,l,mid
#define rson rt << 1 | 1,mid + 1,r
const int maxn = 1e6 + 10;
const int maxm = 2e5 + 10;
ll sum[maxn << 2],val[maxn << 2];
int head[maxm],nxt[maxm],to[maxm],cnt;
ll n,T,a[maxm];
int x[maxm],t[maxm];
ll V[maxm],dp[maxm];
void init() {
	memset(head,-1,sizeof head);
	cnt = 0;
	a[0] = sum[0] = val[0] = 0;
}
void add(int u,int v) {
	to[cnt] = v;
	nxt[cnt] = head[u];
	head[u] = cnt++;
}
void build(int rt,int l,int r) {
	val[rt] = sum[rt] = 0;
	if(l == r) return;
	int mid = l + r >> 1;
	build(lson);build(rson);
}
void upd(int p,int v,int rt,int l,int r) {
	sum[rt] += 1ll * p * v;
	val[rt] += v;
	if(l == r) return ;
	int mid = l + r >> 1;
	if(p <= mid) upd(p,v,lson);
	else upd(p,v,rson);
}
ll qry(ll v,int rt,int l,int r) {
	if(!rt) return 0;
	ll ans = 0,res = 0;
	while(l < r) {
		int mid = l + r >> 1;
		if(res + sum[rt << 1] >= v) {
			rt = rt << 1;
			r = mid;
		}
		else {
			ans += val[rt << 1];
			res += sum[rt << 1];
			rt = rt << 1 | 1;
			l = mid + 1;
		}
	}
	if(res <= v) {
		ll L = 0,R = val[rt] + 1;
		while(L < R) {
			ll mid = L + R >> 1;
			if(res + 1ll * mid * l > v)
				R = mid;
			else L = mid + 1;
		}
		if(L != 0) {
			res += 1ll * (L - 1) * l;
			ans += L - 1;
		}
	}
	return ans;
}
void dfs(int u,int fa) {
	a[u] += a[fa];
	upd(t[u],x[u],1,1,maxn - 10);
	for(int i = head[u]; i + 1; i = nxt[i])
		dfs(to[i],u);
	V[u] = qry(T - 2 * a[u],1,1,maxn - 10);
	upd(t[u],-x[u],1,1,maxn - 10);
}
void dfs2(int u,int fa) {
	dp[u] = V[u];
	ll a = 0,b = 0;
	for(int i = head[u];i + 1; i = nxt[i]) {
		dfs2(to[i],u);
		if(dp[to[i]] > a) {
			b = a;
			a = dp[to[i]];
		}
		else if(dp[to[i]] > b) {
			b = dp[to[i]];
		}
	}
	if(u != 1) dp[u] = max(dp[u],b);
	else dp[u] = max(dp[u],a);
}
int main() {
	scanf("%lld%lld",&n,&T);
	init();
	for(int i = 1; i <= n; i++)
		scanf("%d",&x[i]);
	for(int i = 1; i <= n; i++)
		scanf("%d",&t[i]);
	for(int i = 2; i <= n; i++) {
		int p;scanf("%d%lld",&p,&a[i]);
		add(p,i);
	}	
	build(1,1,maxn - 10);
	dfs(1,0);
	dfs2(1,0);
	printf("%lld\n",dp[1]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值