算法竞赛——进阶指南——acwing 357. 疫情控制 二分+树上倍增+思维+贪心

一道树上难题!

首先:时间肯定越长越好,我们可以二分转化为判规定时间是否可行从而简化问题。

然后我们发现,一个节点肯定越靠近根节点其管辖的节点越多,也越优。

在规定时间内,所有节点尽量往根节点靠近。

执行完靠近后出现两类军队:

1:停在根节点的儿子节点。

2:停在非 根节点的儿子节点。

第1类军队涉及到是否移动到根节点再移动到某个根节点的儿子节点。

我们把根节点的儿子节点的集合设为S。

先不考虑第一类节点移动的问题。

只考虑第二类军队。dfs判断只用第二类节点是否能控制住根节点的某些儿子节点。

把S中仍然未被控制的点集设为H。(某个节点被控制的条件:要么它本身存在一个军队,或者其所有儿子节点都已经被控制)

对于点集H。

这时候考虑第一类军队。这些军队既可以留在当前节点控制当前节点,也可以通过根节点到达S中其他节点。

设军队剩余可移动量为res。停留当前节点不花费移动力,而到其他节点花费移动力。

这导致我们不能统一贪心的分配军队。

我们思考发现:

当某个节点x存在一个军队t,其移动力剩余量小于 d[x]*2(d[x]为x节点到根节点的距离),则如果这个节点没被第二类军队控制,则这个节点一定是通过当前节点的第一类军队 进行控制。

否则其余节点y的军队需要花费d[y]+d[x],而其本身的军队到其他节点需要d[x]+d[z],显然需要d[z]<d[x]。

则不如y直接到z优。(因为y剩余的移动力到达的点大于等于 军队t到达的节点 )

 

而当军队t的移动力大于等于d[x]*2时,其先到达根节点,剩余移动力完全可以再返回x,这类军队可以统一进行分配。

所以我们可以这样贪心:

先把所有第一类军队的res从小到大排序。

如果当前军队t所在节点x尚未被控制,且其行动力小于d[x]*2,则把当前节点控制,把军队t的行动力设为0(相当于固定了当前的军队来控制x点)。

其余军队一律统一的贪心分配即可。(按儿子节点距离从大到小,双指针进行分配即可)

 实现细节看代码。

这题思维量还是蛮大的!值得细品

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
const int M = 5e4+7;
int head[M],cnt;
struct EDGE{int to,nxt,w;}ee[M*2];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}
int n,m;
int a[M];//在i点有多少个军队
int f[M][18],dep[M];
int fg[M];//i节点所在子树是否被控制住
ll d[M];
void dfs(int x,int fa)
{
	for(int i=head[x];i;i=ee[i].nxt)
	{
		int y=ee[i].to,w=ee[i].w;
		if(y==fa)continue;
		dep[y]=dep[x]+1;
		d[y]=d[x]+w;
		f[y][0]=x;
		dfs(y,x);
	}
}
void pre()
{
	dep[1]=1;dfs(1,0);
	for(int i=1;i<=17;i++)for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1];
}
struct node{
	int s;ll res;
	bool operator < (const node &r)const {
		return res<r.res;
	}
}p[M];
void gao(int x,int fa) 
{
	int nm=0,tg=0;
	for(int i=head[x];i;i=ee[i].nxt)
	{
		int y=ee[i].to;
		if(y==fa)continue;
		gao(y,x);
		nm++;
		if(fg[y]==1)tg++;
	}
	if(nm&&nm==tg)fg[x]=1;//其不是叶子节点,且所有儿子节点都被控制,则这个点也会被控制 
}
bool ck(ll T)//能否花不到T的时间控制疫情 
{
	memset(fg,0,sizeof(fg));
	int sz=0;
	for(int i=1;i<=m;i++)
	{
		int x=a[i];
		ll res=T;
		for(int j=17;j>=0;j--)
			if(dep[f[x][j]]>1&&res>=d[x]-d[f[x][j]])res-=d[x]-d[f[x][j]],x=f[x][j];
		if(dep[x]==2)p[++sz]=node{x,res};
		else fg[x]=1;
	}
	gao(1,0);
	sort(p+1,p+1+sz);
	for(int i=1;i<=sz;i++)
	{
		int x=p[i].s;ll res=p[i].res;
		if(!fg[x]&&res<=d[x]*2)fg[x]=1,p[i].res=0;
		p[i].res-=d[x];
	}
	sort(p+1,p+1+sz);
	vector<pair<ll,int> >v;
	for(int i=head[1];i;i=ee[i].nxt)
	{
		int y=ee[i].to;
		if(fg[y]==0)v.pb({d[y],y});
	}
	sort(v.begin(),v.end());
	bool flag=true;
	int tp=sz;
	for(int i=v.size()-1;i>=0;i--)
	{
		ll ds=v[i].first;
		if(tp<=0||p[tp].res<ds)
		{
			flag=false;
			break;
		}
		tp--;
	}
	if(flag)return true;
	return false;
}
int main()
{
	cin>>n;
	ll l=0,r=0,ans=0;
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
		r+=w;
	}
	cin>>m;
	for(int i=1;i<=m;i++)scanf("%d",&a[i]);
	pre();
	while(l<=r)
	{
		ll mid=(l+r)/2;
		if(ck(mid))ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans<<endl;
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值