P1084 疫情控制
好像二分、倍增、树上差分是比较热门的考尻点
会结合在一起考,难度比较大,需要多加练习。
现在在解决noip最后的几道大题,很鹅心。也没有人做向导,很难受qwq
首先这是一棵树,一个军队肯定是越往上走越好。(有大佬说过,对于这种提点的题,要是用倍增)
要是时间最短,就是要是用时最长的军队用时最短,使用二分
然后对于一个走不到首都的军队来说,也肯定是所处深度越小越好
对于可以到达首都的军队来说(不在其子树停留,不过要保存其在那颗子树),我们先通过dfs遍历一遍,找出还有那些子树没有被封锁,再考虑派军队过去,到根节点的儿子,讲这一颗子树封锁
然后我们贪心的派军队。用时小的路径,拍剩余时间少的军队。如果剩余时间最少的军队不能过去,则让其返回其子树的根节点。
如果如此操作之后,仍有子树没有被封锁,则需要加大时间
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using std::sort;
const int maxn=50100;
int n,m;
struct node
{
int p;
int w;
int nxt;
};
struct Data
{
int n1;
int n2;
};
Data need[maxn],army[maxn];
node line[maxn<<1];
int head[maxn],tail;
int f[maxn][22];
int dis[maxn][22];
int nt,at;
int stop[maxn];
int pos[maxn];
int son[maxn];
void add(int a,int b,int c)
{
line[++tail].p=b;
line[tail].w=c;
line[tail].nxt=head[a];
head[a]=tail;
}
int read()
{
int res=0;
char c=getchar();
while(c>'9'||c<'0') c=getchar();
while(c>='0'&&c<='9')
{
res=(res<<3)+(res<<1)+c-'0';
c=getchar();
}
return res;
}
void dfs(int now,int fa,int d)
{
for(int i=1;(1<<i)<=d;i++)
{
f[now][i]=f[f[now][i-1]][i-1];
dis[now][i]=dis[now][i-1]+dis[f[now][i-1]][i-1];
}
for(int i=head[now];i;i=line[i].nxt)
if(line[i].p!=fa)
{
f[line[i].p][0]=now;
dis[line[i].p][0]=line[i].w;
dfs(line[i].p,now,d+1);
}
}
bool compare(const Data &a,const Data &b)
{
return a.n1<b.n1;
}
bool Dfs(int now,int fa)
{
if(stop[now]) return true;
int son=0;
for(int i=head[now];i;i=line[i].nxt)
if(line[i].p!=fa)
{
son++;
if(!Dfs(line[i].p,now))
return false;
}
if(!son) return false;
return true;
}
bool check(int t)//t是最长的时间
{
nt=at=0;
memset(stop,false,sizeof(stop));//stop为节点是否被封锁的标记
for(int i=1;i<=m;i++)
{
int used=0,now=pos[i];
for(int j=19;j>=0;j--)
if(f[now][j]&&f[now][j]!=1&&used+dis[now][j]<=t)//向上提点
{
used+=dis[now][j];
now=f[now][j];
}
if(f[now][0]==1&&t-used-son[now]>=0)//可以到达首都
{
army[++at].n1=t-used-son[now];//存储下来
army[at].n2=now;//子树也要存下来,以便下面返回无法调度的军队
}
else
stop[now]=true;
}
for(int i=head[1];i;i=line[i].nxt)
if(!Dfs(line[i].p,1))//判断一个子树是否被完全封锁
{
need[++nt].n1=son[line[i].p];
need[nt].n2=line[i].p;//如果没有,存下来
}
else
stop[line[i].p]=true;
sort(army+1,army+1+at,compare);//排个序
sort(need+1,need+1+nt,compare);
int i=1,j=0;
for(;i<=nt;i++)
{
if(stop[need[i].n2]) continue;//如果这个子树被之前退回的军队封锁
for(j+=1;j<=at;j++)
{
if(need[i].n1>army[j].n1)//时间不够了,我们就不能让他待在首都,退回去
stop[army[j].n2]=true;//顺便将退回去的子树封锁了
if(stop[need[i].n2]||need[i].n1<=army[j].n1)//当前子树已经被封锁了,若找到了一个可以派遣的剩余时间最少的军队
{
stop[need[i].n2]=true;//打上封锁标记
break;
}
}
if(j==at+1) return false;//军队用完了,而还有子树没有被封锁
}
return true;
}
int main()
{
n=read();
int a,b,c;
for(int i=1;i<n;i++)
{
/*scanf("%d%d%d",&a,&b,&c);*/
a=read(),b=read(),c=read();
add(a,b,c);add(b,a,c);
}
dfs(1,0,1);//预处理倍增
m=read();
for(int i=1;i<=m;i++)
pos[i]=read();
int l=0,r=200000;
for(int i=head[1];i;i=line[i].nxt)
son[line[i].p]=line[i].w;//先预处理出来从根到其子树的距离,其实没有什么用qwq
while(l<r)//二分答案
{
int mid=(l+r)>>1;
if(check(mid))
r=mid;
else
l=mid+1;
}
if(l!=200000) printf("%d",l);
else printf("-1");
return 0;
}