[NOIP2012提高组]疫情控制

题目:洛谷P1084、codevs1218、Vijos P1783。

题目大意:有一棵n个节点的,根为1的带权树和m支军队。每支军队可以在一个点上停下,那么从1开始就不能经过这个点了。现在有m支军队已经在某些点上,移动一支军队到一个相邻的点所花时间等于该条边的边权。军队可同时移动。问至少多少时间才可以使从1开始都到不了任何一个叶子节点(无法满足条件输出-1,根节点不能停军队)。

解题思路:首先,这道题是要求最少的时间,可以二分答案

然后是一个贪心的思路:让每支军队尽可能向根节点爬

因为军队越往上,能封住的叶子节点就越多(至少不会减少),那我们在能往上的情况下,尽可能往上。

一支军队如果不能到根节点,就让其在能爬到的最高点停下,并封锁这个节点。

如果能则记录下它剩下的时间和它经过的<根节点的儿子>的编号(即它从哪个儿子过来的)。

然后我们dfs出已经被封锁的节点,如果一个节点的所有儿子都被封锁,则该节点也算被封锁。

之后我们把所有<没被封锁的,根节点的儿子>的编号和到根节点的距离记录下来。

再然后,我们的任务就是把刚刚记录的那些节点,用剩下的能到达根节点的军队去覆盖

又是贪心,把节点按照到根节点的距离升序排序,把军队按照剩余时间升序排序,然后对于一个节点,用剩余时间最少的,且比这个节点到根节点的距离大(因为要走过去)的军队覆盖。

注意:如果一个军队“经过的<根节点的儿子>”没有被覆盖,则优先覆盖。

最后如果能成功覆盖所有根节点的儿子,则说明答案可行,否则不可行。

对于让军队向上爬的过程,我们可以用倍增的方法进行优化,所以要先预处理。

整理思路:

 ①预处理倍增。

 ②二分答案。

 对于每个二分的答案:

   ①让每个军队尽可能向上爬。对于不能到根节点的,让其在能到达的最高节点停下,否则记录下来。对记录数据排序。

   ②找出所有未被封锁的<根节点的儿子>记录。对记录数据排序。

   ③用剩余军队覆盖所有未被封锁的根节点的儿子。判断答案是否可行。

以下对时间复杂度分析:

预处理时间复杂度$O(n)$(dfs)+$O(nlog_2 n)$(处理倍增数组)。

判断答案时间复杂度$O(mlog_2 n)$(模拟军队上移)+$O(n)$(找未被封锁的军队)+$O(nlog_2 n+mlog_2 n)$(排序)+$O(n+m)$(贪心,覆盖未被封锁的节点),这些都要乘上二分复杂度$O(log_2 n)$。

故总时间复杂度$O((n+m)log^2 n)$。

我不会告诉你我因为一个逗号分隔符两边表达式写反了而调试了一个半小时的

C++ Code:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 50005
#define reg register
#define ll long long
int n,m,head[N],cnt,p[N],dep[N],f[N][17],cnta,cntb;
ll g[N][17];
bool vis[N];
struct nd{
	ll num;
	int d;
	bool operator <(const nd& rhs)const{return num<rhs.num;}
}a[N],b[N];
struct edge{
	int to,dis,nxt;
}e[N<<1];
inline int readint(){
	reg char c=getchar();
	for(;!isdigit(c);c=getchar());
	reg int d=0;
	for(;isdigit(c);c=getchar())
	d=(d<<3)+(d<<1)+(c^'0');
	return d;
}
void Dfs(int now){//预处理 
	for(reg int i=head[now];i;i=e[i].nxt)
	if(!dep[e[i].to]){
		dep[e[i].to]=dep[now]+1;
		f[e[i].to][0]=now;
		g[e[i].to][0]=e[i].dis;
		Dfs(e[i].to);
	}
}
void feng(int now){
	if(vis[now])return;
	vis[now]=true;
	reg bool lt=true;
	for(reg int i=head[now];i;i=e[i].nxt)
	if(dep[now]<dep[e[i].to]){
		lt=false;
		feng(e[i].to);
		vis[now]&=vis[e[i].to];
	}
	if(lt)vis[now]=false;
}
bool ok(ll k){//判断 
	reg int x;
	reg ll sum;
	cnta=cntb=0;
	memset(vis,0,sizeof vis);
	memset(a,0,sizeof a);
	memset(b,0,sizeof b);
	for(reg int i=1;i<=m;++i){
		x=p[i],sum=0;
		for(reg int j=16;j>-1;--j)//让军队爬上来 
		if(f[x][j]>1&&sum+g[x][j]<=k)
		sum+=g[x][j],x=f[x][j];
		if(f[x][0]==1&&sum+g[x][0]<=k){
			a[++cnta]=(nd){k-sum-g[x][0],x};
		}else vis[x]=true;
	}
	feng(1);//搜索已经被封的节点 
	if(vis[1])return true;
	for(reg int i=head[1];i;i=e[i].nxt)
	if(!vis[e[i].to])b[++cntb]=(nd){e[i].dis,e[i].to};
	if(cnta<cntb)return false;
	reg int zz2=1;
	sort(a+1,a+cnta+1);
	sort(b+1,b+cntb+1);
	for(reg int i=1;i<=cnta&&zz2<=cntb;++i){//判断答案 
		if(!vis[a[i].d])vis[a[i].d]=true;else{
			while(vis[b[zz2].d]&&zz2<=cntb)++zz2;
			if(zz2>cntb)return true;
			if(a[i].num>=b[zz2].num)vis[b[zz2++].d]=true;
		}
		while(vis[b[zz2].d]&&zz2<=cntb)++zz2;
	}
	return zz2>cntb;
}
int main(){
	reg ll ans=-1,l=0,r=0;
	cnt=0;
	memset(head,0,sizeof head);
	memset(dep,0,sizeof dep);
	memset(f,0,sizeof f);
	memset(g,0,sizeof g);
	n=readint();
	for(reg int i=1;i<n;++i){
		reg int u=readint(),v=readint(),t=readint();
		r+=t;
		e[++cnt]=(edge){v,t,head[u]};
		head[u]=cnt;
		e[++cnt]=(edge){u,t,head[v]};
		head[v]=cnt;
	}
	m=readint();
	for(reg int i=1;i<=m;++i)p[i]=readint();
	dep[1]=1;
	Dfs(1);
	for(reg int j=1;j<17;++j)//预处理++ 
	for(reg int i=1;i<=n;++i){
		f[i][j]=f[f[i][j-1]][j-1];
		g[i][j]=g[f[i][j-1]][j-1]+g[i][j-1];
	}
	while(l<=r){//二分 
		reg ll mid=(l+r)>>1;
		if(ok(mid))r=(ans=mid)-1;else
		l=mid+1;
	}
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Mrsrz/p/7770889.html