codeforces1436D Bandit in a City (#678 Div. 2)

题目链接:

https://mp.csdn.net/console/editor/html?not_checkout=1

题目大意:

给你一颗树,强盗要从树的根节点(节点1)出发,每个节点会有一些市民
每回合,每个市民们可以从当前节点跑到其子节点上,每个市民跑完之后,
强盗会选择其所在当前节点的一个子节点,像其移动。当强盗到一个叶节点上时
其会抓住改节点的所有市民。
强盗想抓住最多的市民
每个市民都想让强盗抓住的市民总数最少
现如果强盗按照最聪明的方式移动,市民们也按照最聪明的方式移动,求最终强盗能
抓住多少市民

输入与输出:

 

input:

一个n代表给定树有n个节点
接下来一行有n-1(2~n)个数字ai,代表第i个节点的父亲为ai
接下来一行有n个数字bi,代表第i个节点一开始有bi个市民
(2 <= n <= 2e5, 0 <= bi <= 2e5)

output:

一个数,代表强盗抓住的市民数量
思路:
这道题的题意比较绕,其实可以转化为一开始每个节点有bi个市民,
他们每个人都开始像叶节点走,问在各种走法中最后包含有最多的
市民的叶节点最少有可能为多少。

思路:

(1)复杂度nlogn:

我们可以先二分我们要求的答案,设我们每次二分到的答案为pres,
则,对于每一个叶节点,其最多能容纳pres的市民,而对于每一个非叶节点i
其能获得的容量为: 其所有(直接)子节点能贡献给他的容量
其需要的容量便为: bi
其能贡献给其父亲的容量为:其能获得的容量-bi
我们对于每次的二分结果都去验证一遍是否每个节点都能满足即可

(2)复杂度n:

设每个节点所对应的的子树所包含的叶节点为mi
设每个节点所对应的的子树所包含的市民数量为sum-bi

对于每个节点,其都有一种每个节点理想解(tres)<= 真实解(res)为:
向上取整(sum-bi / mi)
而我们对于每一个节点都求一个tres,然后取一个max便为res。
我们可以把tres分为两种:
1.不真实的tres:
因为其tres是让其子树所有节点的市民先走到他那里,但这是不可能的。
但因为tres < res 我们又是取的max,所有其实对答案没影响。

(图中绿色的为子树最优解,黄色的为当前节点的市民)


2.真实的tres:
如果tres是真是对答案有影响的,那么其tres = res的

 

 

代码:

(顶部的_runSt宏 为1用的是方法2, 为 0 用的是方法1)

#define _runSt 1
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 2e5 + 5;
typedef long long LL;

int xx[maxn], n;
LL dp[maxn][2], res;
vector<vector<int>> vec(maxn);

void dfs(int ii, int fa)
{
	bool isF = 1;
	dp[ii][0] = xx[ii];
	for (int i = 0; i < vec[ii].size(); i++) {
		isF = 0;

		int e = vec[ii][i];
		dfs(e, ii);
		dp[ii][0] += dp[e][0];
		dp[ii][1] += dp[e][1];
	}
	if (isF) dp[ii][1] = 1;
	res = max(res, dp[ii][0] / dp[ii][1] + bool(dp[ii][0] % dp[ii][1]));
}

int main()
{
	scanf("%d", &n);
	for (int i = 2; i <= n; i++) {
		int x;
		scanf("%d", &x);
		vec[x].push_back(i);
	}
	for (int i = 1; i <= n; i++)
		scanf("%d", &xx[i]);
	dfs(1, 1);
	printf("%lld\n", res);
	return 0;
}

#if _runSt
#else
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 2e5 + 5;
typedef long long LL;
vector<vector<int>> vec(maxn);
int n, numsLv, xx[maxn], out[maxn];
LL yy[maxn];
LL MAX_CC = 1e15, findcc;
bool canThis;

LL dfs(int ii, int fa)
{
	LL thiscc = 0;
	bool isL = 1;

	for (int i = 0; i < vec[ii].size(); i++) {
		isL = 0;

		int e = vec[ii][i];
		thiscc += dfs(e, ii);
		if (thiscc > MAX_CC) thiscc = MAX_CC;
		if (!canThis) return -1;
	}
	if (isL) thiscc = findcc;
	//if (findcc == 2) {
	//	printf("xx[%d]=%d fa=%d thiscc=%lld\n", ii, xx[ii], fa, thiscc);
	//}
	if (thiscc >= xx[ii])
		return thiscc - xx[ii];
	else canThis = 0;
	return -1;
}

bool chk(LL mm) {
	canThis = 1;
	findcc = mm;
	dfs(1, 1);
	return canThis;
}

int main()
{
	scanf("%d", &n);
	for (int i = 2; i <= n; i++) {
		int x;
		scanf("%d", &x);
		vec[x].push_back(i);
	}
	for (int i = 1; i <= n; i++) 
		scanf("%d", &xx[i]);
	
	LL lw = 0, hg = MAX_CC, res = -1;
	while(lw <= hg){
		LL mm = (lw + hg) >> 1;
		if (chk(mm)) hg = mm - 1, res = mm;
		else lw = mm + 1;
	}
	printf("%lld\n", res);
	return 0;
}
#endif

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值