P1084 疫情控制

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;
}
posted @ 2018-08-01 16:01  Lance1ot  阅读(275)  评论(0编辑  收藏  举报