【NOIP2012】疫情控制【倍增】【二分答案】【贪心】【思维分析】

6 篇文章 0 订阅
2 篇文章 0 订阅

传送门

倍增只是手段,重点是思维分析的过程。wuvin曾经说过,只要有好的思维,什么神题也打不倒我们。

军队可以移动,叶子节点不能被根节点到达,最短时间。

发现二分

首先发现时间越长,就越可能管辖成功。有这种显然的单调性促使我们二分找临界点。

对于一个限定的时间,我们来想想怎么判定是否合法。

发现越高越好

首先,我们发现,能管辖的越高越好,一次可以管辖更多的子树。所以我们可以尽力往上走。

但是有个问题:如果走到了根节点,那往哪里走呢?

不慌,遇事不决先存下来。不过走到根节点付出了一定时间,所以我们需要记录还能使用的时间和军队编号。

如果走不到?那就将这个最高能走到的地方打上一个标记,表示这个子树可以被管辖了。

在进行完这一步后,所有军队都要么到了1,要么到了自己能走的最高点。现在我们获得了一些可以从根节点出发的军队,和一个被部分管辖的树。

如果这个树已经被管辖了,那就可以宣告合法了。dfs一次查有没有被管辖。

如果自己被管辖或者自己的所有儿子被管辖都是合法的。

反之,从根节点出发,走一步就可以控制着一大坨儿子。那我们就不需要往下走。

遇事不决先存下来,将没有被完美管辖的根节点的儿子存下来,包括它们和根节点的距离。

然后问题变成了:有一些需要军队的子树,和一个剩余时间不一的军队集合。我们要分配这些军队。

很明显剩余时间长的去找远的子树,短的找近的,这样肯定最优。

但我们突然发现一个事情:如果一个军队本来可以轻松走到自己下面的一个子树做管辖,却一不小心走到了根节点,又走不回去了,这怎么办呢?

遇事不决先预处理。我们大可以在给每支军队往上走的时候,给所有根节点的子节点记录一个东西。这个东西是:从其子树上来的,能到根节点的,但剩余时间又最少的。

为什么最少呢?因为这样就可以减少损失。让最远的那个军队来占领它。

很明显,在使用当前剩余最大的那个剩余军队之前,如果能使用这个很小的,我们当然使用这个很小的。

那么就差不多了。

但是复杂度是n方logn的,我们来找优化。

很明显,只有在往上跳的这个过程可以简化。直接倍增就可以。

但是因为要处理每个根节点的子节点的最少剩余军队,所以倍增的时候不要直接跳到根。这样方便处理。

如此甚好,,差不多了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int n;
int first[100003],nxt[100003],to[100003],w[100003],tot;
void add(int a,int b,int c){
	nxt[++tot]=first[a];first[a]=tot;to[tot]=b;w[tot]=c;
}
int m;int army[100003];
int fa[50003][17],dis[50003][17];
struct node{
	int rest,id;
}a[50003];
struct bili{
	int rest,id;
}b[50003];
int flag[50003],vis[50003],use[50003],cnta,cntb,minrest[50003];
void dfs0(int u,int faa){
	fa[u][0]=faa;
	for(int i=1;i<=16;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=1;i<=16;i++)dis[u][i]=dis[u][i-1]+dis[fa[u][i-1]][i-1];
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==faa)continue;dis[v][0]=w[i];dfs0(v,u);
	}
}
bool dfs2(int u,int faa){
	if(vis[u])return true;int flag=0,fg=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==faa)continue;
		flag=1;
		if(!dfs2(v,u)){fg=0;
			if(u==1){b[++cntb].rest=w[i];b[cntb].id=v;}
			else return false;
		}
	}if(!flag)return false;return fg;
}
bool cma(node a,node b){
	return a.rest>b.rest;
}
bool cmb(bili a,bili b){
	return a.rest>b.rest;
}
bool check(int mid){
	memset(flag,0,sizeof(flag));memset(vis,0,sizeof(vis));memset(use,0,sizeof(use));cnta=cntb=0;
	for(int i=1;i<=m;i++){
		int u=army[i],num=0;
		for(int j=16;j>=0;j--){
			if(fa[u][j]>1&&num+dis[u][j]<=mid){
				num+=dis[u][j];u=fa[u][j];
			}
		}
		if(fa[u][0]==1&&num+dis[u][0]<=mid){
			a[++cnta].rest=mid-num-dis[u][0];a[cnta].id=i;
			if(!flag[u]||a[cnta].rest<minrest[u]){
				minrest[u]=a[cnta].rest;flag[u]=i;
			}
		}
		else vis[u]=1;
	}
	if(dfs2(1,0))return true;
	sort(a+1,a+cnta+1,cma);
	sort(b+1,b+cntb+1,cmb);
	int now=1;use[0]=1;
	for(int i=1;i<=cntb;i++){
		if(!use[flag[b[i].id]]){
			use[flag[b[i].id]]=1;continue;
		}
		while(now<=cnta&&(use[a[now].id]||a[now].rest<b[i].rest))++now;
		if(now>cnta)return 0;use[a[now].id]=1;
	}return 1;
}
int main(){
	n=in;
	for(int i=1;i<n;i++){
		int a=in;int b=in;int c=in;add(a,b,c);add(b,a,c);
	}dfs0(1,0);
	m=in;
	for(int i=1;i<=m;i++)army[i]=in;
	int l=0,r=500003;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}if(l==500003)cout<<"-1";else cout<<l; 
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值