1436 D. Bandit in a City(两种解法:二分法或者思维)

传送门

先说点题外话,我觉得二分也是完全ok的,但是在测试点六溢出了…

后来证明算法是正确的,数据卡了二分…设置以下不让 f f f数组溢出就行了

思路就是二分最大值,而后假定所有叶子节点都是最大值,向上合并

这样就知道每个节点有多少居民是上面传递下来的,多了没事,但如果少了

就存在节点的居民没分配完的情况,就是不合法的

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=4e5+10;
int t,n,mid,in[maxn],l,r;
struct edge{
	int to,nxt;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v){
	d[++cnt]=(edge){v,head[u]},head[u]=cnt;
}
int f[maxn],a[maxn],flag;
void dfs(int u,int fa)
{
	if( !flag )	return;
	int ok=0;	f[u]=0;
	for(int i=head[u];i;i=d[i].nxt )
	{
		int v=d[i].to;
		ok=1; dfs(v,u);
		if( f[u]<=1e17 )
			f[u]+=f[v];
	}
	if( ok==0 )	f[u]=mid-a[u];
	else
	{
		f[u]-=a[u];
		if( f[u]<0 )	flag=0;
	}
}
bool isok(int mid)
{
	flag=1;
	dfs(1,0);
	return flag;
}
signed main()
{
	cin >> n;
	int s; 
	for(int i=2;i<=n;i++)
	{
		int x; scanf("%lld",&x);
		add(x,i);	
		in[i]++,in[x]++;
	}
	l=0,r=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		r += a[i];	
	}	
	for(int i=2;i<=n;i++)
		if( in[i]==1 )	l=max(l,a[i]);
	int ans=0;
	while( r>=l )
	{
		mid = l+r>>1;
		if( isok(mid) )	r=mid-1,ans=mid;
		else	l=mid+1;	
	}
	cout << ans;
} 

标称的做法

如果只有根节点有居民,答案是 [ a 1 / l e a f 1 ] [a_1/leaf_1] [a1/leaf1]向上取整

意思是 a 1 a_1 a1个居民分配到 l e a f leaf leaf个叶子节点去

但是情况不是这样,有些节点本身就有比较多的居民

预处理 s u m i sum_i sumi为节点 i i i为根的子树内的总居民数量

l e a f i leaf_i leafi为节点 i i i为根的子树内的总叶子节点数量

假如最后最大的居民在以 u u u为根的子树内

Ⅰ . \color{Red}Ⅰ. .

如果存在一个儿子 v v v,使得就算不给 v v v分配一个居民,最后还是 v v v子树内的叶子节点居民最大,那么就把问题规模缩小成以 v v v为根的子树了(其他儿子就没用了)

Ⅱ . \color{Red}Ⅱ. .

如果不存在这种儿子 v v v,就存在一种分配方式使得两边尽量平均

此时在 u u u的子树内最大的叶子节点就是 [ s u m i / l e a f i ] [sum_i/leaf_i] [sumi/leafi]

所以答案就是最大的 [ s u m i / l e a f i ] [sum_i/leaf_i] [sumi/leafi]

因为所有的情况一最后都变成了情况二

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
#define int long long
struct edge{	
	int to,nxt;
}d[maxn]; int n,head[maxn],cnt=1;
void add(int u,int v){
	d[++cnt]=(edge){v,head[u]},head[u]=cnt;
}
int sumn[maxn],leaf[maxn],a[maxn];
void dfs(int u)
{
	int ok=0;
	sumn[u] = a[u];
	for(int i=head[u];i;i=d[i].nxt )
	{
		int v=d[i].to;
		dfs(v); ok=1;
		leaf[u]+=leaf[v];
		sumn[u]+=sumn[v];
	}
	if( !ok )	leaf[u]=1;
}
signed main()
{
	cin >> n;
	for(int i=2;i<=n;i++)
	{
		int x; scanf("%lld",&x);
		add(x,i);	
	}
	for(int i=1;i<=n;i++)	scanf("%lld",&a[i]);
	int ans=0;
	dfs(1);
	for(int i=1;i<=n;i++)
		ans = max( ans,sumn[i]/leaf[i]+(sumn[i]%leaf[i]!=0) );
	cout << ans;
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值