疫情控制

给定一棵树 给定一些障碍,用这些障碍来切断根节点到所有叶子节点的路径,可以移动障碍,求总时间最少

可能会发生这样的一种情况: 当前子树的军队去管辖别的子树 当前子树被别的军队管辖

正确且简便的思路应当是这样的:
1.二分答案 在二分的时间内可以分为到达根的节点和没有到达根的节点
2.所有能够到达根的节点排序后去选择尚未被控制的子树

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define mp make_pair
#define fs first
#define sc second
using namespace std;
const int N=5e4+10;
const int M=N*2;
const int INF=0x3f3f3f3f;
int read()
{
	int x=0,f=0,c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return f?-x:x;
}

struct Edge
{
	int to,next,w;
}e[M];
int head[N],cnt;
void _add(int a,int b,int c){ e[++cnt]=(Edge){b,head[a],c}; head[a]=cnt;}
void add(int a,int b,int c){ _add(a,b,c); _add(b,a,c); }

int n,m;
int army[N],bel[N],tot,tim[N],isarmy[N];
pair<ll,int> p[N];
ll dep[N],l,r,d[N];
bool v[N],iscon[N];

void dfs(int x,int fa,int t)
{
	bel[x]=t; 
	for(int i=head[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa) continue;
		dep[y]=dep[x]+e[i].w;
		dfs(y,x,t);
	}
}

ll T(int x){ return dep[army[x]]-dep[p[bel[army[x]]].sc];}
bool cmp(int x,int y){return T(x)+p[bel[army[x]]].fs>T(y)+p[bel[army[y]]].fs;}

void trycon(int x,int ff,ll mid)
{
	d[x]=INF; bool flag=0,tmp=1;
	if(isarmy[x]){d[x]=0; iscon[x]=1; return ;}
	for(int i=head[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==ff) continue;
		flag=1;
		trycon(y,x,mid);
		d[x]=min(d[x],d[y]+e[i].w);	tmp&=iscon[y];
	}
	if(d[x]<=mid || (flag&&tmp) ) { iscon[x]=1; return ;}
}

bool check(ll mid)
{
	memset(v,0,sizeof v);
	memset(iscon,0,sizeof iscon);
	for(int i=1;i<=m;i++)
		if(T(i)<=mid) v[i]=1,isarmy[army[i]]--;
	for(int i=1;i<=tot;i++) 
		trycon(p[i].sc,1,mid);
	int t1=1,t2=1;
	while(t1<=m&&t2<=tot)
	{
		int t=bel[army[tim[t1]]];
		if(iscon[p[t2].sc]) t2++;
		else if(!v[tim[t1]]) t1++;
		else if(mid-T(tim[t1])<p[t].fs+p[t2].fs) t1++,iscon[p[t].sc]=1;
		else iscon[p[t2++].sc]=1, t1++;
	}	
	for(int i=1;i<=m;i++) 
		if(v[i]) isarmy[army[i]]++;
	for(int i=1;i<=tot;i++) 
		if(!iscon[p[i].sc]) return false;	
	return true;
}

int main()
{
	n=read();
	for(int i=1;i<n;i++) 
	{
		int x=read(),y=read(),z=read(); r+=z;
		if(x==1||y==1) p[++tot]=mp(z,x+y-1);
		add(x,y,z);
	}
	m=read();
	if(m<tot){ puts("-1"); return 0;}
	for(int i=1;i<=m;i++) army[i]=read(),tim[i]=i,isarmy[army[i]]++;
	sort(p+1,p+tot+1);	
	for(int i=1;i<=tot;i++) 
	{   
		int x=p[i].sc; dep[x]=p[i].fs;
		dfs(x,1,i); 
	}
	sort(tim+1,tim+m+1,cmp);
	while(l+1<r)
	{
		ll mid=(l+r)>>1; 
		if(check(mid)) r=mid;
		else l=mid;
	}
	printf("%lld",r);
	return 0;
}
//p: 第i个子树的节点编号,距离(从小到大) 
//army: 第i个军队的位置 
//tim: 所需要时间从大到小排名为i的军队编号 
//T: 第i号军队所需要的时间 
//bel:每个节点属于哪一个子树

诸多技巧:

  1. 树上问题的叶子结点要特别注意是不是需要特判
  2. 映射已经映射的值
  3. 考虑到0这一种特殊的情况
  4. while中嵌套else if
posted @ 2022-01-20 14:41  __iostream  阅读(34)  评论(0)    收藏  举报