洛谷P1084 树上问题,思维,贪心,二分答案

本文探讨了在一个由n个城市组成的国家中,如何最高效地调动m个军队,在各条通往边境城市的道路上设立检查站的问题。通过二分查找和贪心算法确定在限定时间内最优的检查站分布方案。

题意:

HHH国有nnn个城市,用n−1n-1n1条道路连接着,其中111是首都,发生了疫情,为了使边境城市(叶子结点)不被感染,需要在所有从首都通往边境城市的路上设置至少一个检查站,一个检查站需要一个军队管辖。HHH国有mmm个军队,目前分别驻扎在aia_{i}ai城市,现在可以调遣军队,他们同时出发,问最快能在什么时候使所有首都通往边境城市的道路上都至少存在一个检查站?军队每个小时行进111个单位的距离

Solution:

如果检查站深度越低,显然他能为更多的道路做出贡献,因此,军队要么被调拨至111的其他儿子处,要么在当前位置到111的链的深度更浅的位置,这样的决策一定是更优的,有点难操作,相互制约因素有点多,我们要使耗时最多的军队耗时最少,不妨二分答案,假设二分时间为xxx,考虑怎么检查是否在xxx时间内达成目的

由于深度越低,贡献越大,所以我们不妨在xxx时间内,一直向上走,由上面的分类,我们可以考虑是否让某只军队iii越过111这个位置,设uuu111的儿子中,子树包含了aia_{i}ai的城市

如果xxx时间并不足以让iii到达111,那么他最高能到达哪里,他在这个位置就一定最优

如果xxx时间足以让iii到达111,设到达111位置后还剩下的时间为restrestrest,这样的军队能够被调拨至111的另外的儿子处,同时,它也可以重新返回uuu,管辖他所在的那一颗子树,我们该怎么决策呢?可以这样考虑,我们将所有能够到达111iiirestrestrest排序,再将所有111到他所有没有被管辖的儿子的距离排序,现在要做的就是用restrestrest跟距离配对。贪心想法自然是小的配对小的,大的配对大的,尽量物尽其用,但此时如果可以返回uuu,并且如果uuu没有被管辖,返回uuu一定是更优的,证明过程如下:

我们的配对顺序如下

bool check()
{
    sort(rest+1,rest+1+n);
    sort(dis+1,dis+1+m);
    for(int i=1,j=1;i<=n;i++)
        if(j<=m&&rest[i]>=dis[j]) j++;
    return j==m+1;
}

显然,我们配对过的jjj一定是从左到右的,所以如果iiiuuu还没有被配对,此时一定出现在已配对完指针的右边,由于disdisdis被排过序,他的disdisdis一定大于等于现在等待匹配的,也就是我们能用目前的rest[i]rest[i]rest[i],来消去一个不会更小的dis[j]dis[j]dis[j],这样一定是更赚的,这是没有iii返回过的情况,同理可以得到,就算有iii返回过,也是更赚的

最后,能否在xxx时间内到达111,只需要看深度即可,并且需要处理每个iiiuuu,需要一步一步往上走,直到父亲为111为止,这一步是可以用倍增优化的,但是需要注意的是,此时是有权树,倍增跳跃的距离不是2i2^i2i。同时,处理完所有不能到达111的军队之后,我们还需要检查子树是否被管辖,所以需要一次dfsdfsdfs,子树uuu被管辖的条件是:uuu有军队,或者uuu的所有儿子都被管辖。

// #include<bits/stdc++.h>
#include<iostream>
#include<ctime>
#include<queue>
#include<complex>
#include<cstdio>
#include<bitset>
#include<stack>
#include<cmath>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;

using ll=long long;
#define int long long
const int N=5e4+5,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;

struct way
{
	int to,next,w;
}edge[N<<1];
int cntt,head[N];

void add(int u,int v,int w)
{
	edge[++cntt].to=v;
	edge[cntt].w=w;
	edge[cntt].next=head[u];
	head[u]=cntt;
}

bool has[N];
int n,m,depth[N],a[N],f[N][21];

void dfs(int u,int fa)
{
	f[u][0]=fa;
	for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to,w=edge[i].w;
		if(v==fa) continue;
		depth[v]=depth[u]+w;
		dfs(v,u);
	}
}

struct node
{
	int rest,from;
}rt[N];

int now[N],cnt;

int jump(int x,int len)
{
	for(int i=20;i>=0;i--)
		if(f[x][i]&&depth[x]-depth[f[x][i]]<=len) len-=depth[x]-depth[f[x][i]],x=f[x][i];
	return x;
}

void dfs1(int u,int fa)
{
	bool flag=true,son=false;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs1(v,u); son=true;
		if(!has[v]) flag=false;
	}
	has[u]=(has[u]||(son&&flag));
}

bool check(vector<pair<int,int>>&tmp)
{
	int j=0;
	for(int i=1;i<=cnt;i++)
	{
		while(j<tmp.size()&&has[tmp[j].first]) j++;
		if(!has[rt[i].from]) has[rt[i].from]=true;
		else if(j<tmp.size()&&rt[i].rest>=tmp[j].second) has[tmp[j++].first]=true;
	}
	while(j<tmp.size()&&has[tmp[j].first]) j++;
	return j==tmp.size();
}

bool check(ll x)
{
	cnt=0;
	for(int i=1;i<=n;i++) has[i]=false;
	for(int i=1;i<=m;i++)
	{ 
		if(depth[a[i]]-1<=x) rt[++cnt]={x-(depth[a[i]]-1),jump(a[i],depth[a[i]]-2),false};
		else has[jump(a[i],x)]=true;
	}
	dfs1(1,0);
	sort(rt+1,rt+1+cnt,[&](const node& x,const node& y){
		return x.rest<y.rest;
	});
	vector<pair<int,int>>tmp;
	for(int i=head[1];i;i=edge[i].next)
		if(!has[edge[i].to]) tmp.push_back({edge[i].to,edge[i].w});
	sort(tmp.begin(),tmp.end(),[&](const pair<int,int>& x,const pair<int,int>& y){
		return x.second<y.second;
	});
	return check(tmp);
}

signed main()
{
	ios::sync_with_stdio(false);
	cin>>n; depth[1]=1;
	for(int i=1;i<n;i++)
	{
		int u,v,w; cin>>u>>v>>w;
		add(u,v,w); add(v,u,w);
	}
	cin>>m;
	for(int i=1;i<=m;i++) cin>>a[i];
	dfs(1,0);
	ll l=1,r=INF,ans=INF;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid))
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	if(ans==INF) ans=-1;
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值