NOIP2012 疫情控制 D2 T3 真·详解''

天了噜
终于改出来了
我的逻辑漏洞好大啊
试了好几组数据突然发现自己好思博
记住
我的二分左开右开
记住
每次查找要初始需要初始的值

记住
递归的逻辑不能乱,否则会死的很惨
记住
递归要考虑所有情况
记住
写代码不要学我

忽略最后一条

首先
一眼就看出了这道题是二分
然后
就是查询的锅了
查询,( ̄▽ ̄)”
果断贪心
所有的军队深度越低越好
那么就让它们尽量靠近根节点
注意
这里有一个小细节
有可能从一开始就不需要向上爬就已经控制了所有点。
但是如果按照我的思路写
这个细节可忽略
然后…
就可以分为两种情况

  1. 可以到达根节点
  2. 不能到达根节点

如果能到达根节点,则处理一下剩余的时间
按照剩余时间大小给所有到达根节点的军队排序
如果不能到达根节点,则标记能到达的最后一个点
表示该点及其儿子已被删除(已被控制)
然后处理一下能到根节点能到达的未被控制完全的儿子
因为要求时间最短,所以到达根节点的军队只需在这一层驻扎就可以了
不必再深入
这里…
还有一个小细节
如果一个能到根节点的军队从起点去根节点的路径未被标记
那么这支军队直接控制这条路径
贪心思想不解释
当然
还有最后一个小细节
开始处于根节点的军队不能呆在根节点

看了网上那么多人又是倍增,又是树剖,还有人用了拓扑排序…
我表示不服
我就给你们写个裸的
我过了

附上代码

#include 
#include 
#include 
#include 
using namespace std;
queue <int> q;//bfs用的队列...
int n,m,cnt,idx,so,tso;//cnt军队个数,idx加边用,so根节点儿子数,tso根节点未控制的儿子数
int head[50001],fa[50001];//head[]用于邻接表头,fa[]存当前点的父亲
bool v[50001];//当前点及其儿孙是否被删除
long long d[50001];//当前点与父亲连接的边的边权
struct edge
{
    int to,next;
    long long len;
}data[100000];//邻接表
void add(int x,int y,int z)
{
    data[++idx].to=y;
    data[idx].len=z;
    data[idx].next=head[x];
    head[x]=idx;
    return ;
}//加边
struct toson
{
    int i;//该儿子是谁
    long long w;
    bool operator <(const toson &s)const
    {
        return w//按照路径权值大小排序
}tos[50001];//根节点可到达的儿子
struct army
{
    int i,from;//i为军队初始位置,from为军队向上走到的深度最低的节点由谁走到,若没走,则为本身
    long long re;//到达根节点后剩余的时间,到达不了则为0
    bool operator <(const army &s)const
    {
        return re//按照到达根节点后剩余时间排序
}ar[50001];//军队
void bfs()//预处理
{
    q.push(1);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=data[i].next)
            if(data[i].to!=fa[x])
            {
                if(x==1)
                    so++;//记录根节点的儿子数
                fa[data[i].to]=x;//预处理父亲
                d[data[i].to]=data[i].len;//预处理边权
                q.push(data[i].to);
            }
    }
    return ;
}
int find(int x,long long ti)//传入军队标号x和当前二分得到的限制时间,查找能到达的深度最深的点
{
    long long sum=0;//已走边权和
    int tmp=ar[x].i;//当前点
    sum+=d[tmp];
    if(tmp==1)//如果军队初始位置为根节点
    {
        ar[x].re=ti;//剩余时间无需计算,必为限制时间
        ar[x].from=0;//没从任何路径到根节点,from为零
        return 1;
    }
    while(sum<=ti&&tmp!=1)//已走边权和必须小于等于限制时间,否则要停下,同时如果到达根节点,停下
    {
        ar[x].from=tmp;
        tmp=fa[tmp];
        sum+=d[tmp];
    }//维护路径
    if(tmp==1)//如果能到达根节点
        ar[x].re=ti-sum;初始剩余时间
    return tmp;//返回能到达的深度最低的点
}
bool dfs(int x)//预处理根节点没被控制的儿子,同时删去没必要的子孙,返回当前点是否被完全控制
{
    if(v[x])//如果当前点被删除
        return true;//返回该儿子已被控制
    bool ans=true;//所有儿子是否都被控制
    int count=0;//能到达的儿子个数
    for(int i=head[x];i;i=data[i].next)
        if(data[i].to!=fa[x])
        {
            count++;
            if(dfs(data[i].to))//如果被控制
                continue;//什么也不做
            else//如果没被控制
            {
                if(x==1)//如果当前点是根节点
                {
                    tos[tso].i=data[i].to;
                    tos[tso++].w=data[i].len;
                }//根节点能到达该儿子
                ans=false;//当前点不是所有儿子都被控制
            }
        }
    if(!count)//如果没有儿子,说明当前点是叶子节点,且当前点未被控制
        return false;//返回当前点未被控制
    if(ans)//如果当前点所有儿子都被控制
    {
        v[x]=true;//当前点已被完全控制
        return true;//返回当前点已被完全控制
    }
    return false;//当前点有儿子,当前点儿子未被完全控制,返回当前点未被完全控制
}
bool check(int x)//答案判定
{
    if(x==-1)
        return false;//左开右开二分的边界
    memset(v,0,sizeof(v));//初始化已删边为全部未删
    tso=0;//初始化未控制儿子数为未计数
    for(int i=0;i//枚举军队
    {
        ar[i].re=0;//初始剩余时间
        ar[i].from=ar[i].i;//初始路径
        int tmp=find(i,x);//tmp为可到达的深度最低点
        if(tmp!=1)//如果到达不了根节点
            v[tmp]=true;//删去能到达的深度最低点控制的子孙
    }
    sort(ar,ar+cnt);//排序军队
    int i=0,j=0;
    while(!ar[i].re&&i//如果当前点到达根节点后剩余时间为零,直接控制来时路径,此处包含到达不了根节点导致剩余时间为零的情况,可以忽略不计
    {
        if(!v[ar[i].from])//如果来时路径未被删除
            v[ar[i].from]=true;//删去来时路径
        i++;//找下一支军队
    }
    dfs(1);//预处理根节点没被控制的儿子,同时删去没必要的子孙
    sort(tos,tos+tso);//排序根节点可到达的儿子
    if(tso>cnt)//如果可到达的儿子数比军队总数还多
        return false;//返回当前解不成立
    while(j//j或i不能超限
    {
        while(ar[i].from&&!v[ar[i].from]&&i//如果当前军队初始位置不在根节点且来时路径未被删除
        {
            v[ar[i].from]=true;//删除该路径
            i++;//查看下一支军队
        }
        if(i==cnt)//如果i达到界限
            break;//弹出
        while(v[tos[j].i]&&j//如果当前根节点可到达的儿子已被删去
            j++;//看下一个
        if(j==tso)//如果j超限
            return true;//弹出
        while(ar[i].re//如果当前军队剩余时间不能到达当前根节点可到达的儿子
            i++;//找下一个军队
        if(i==cnt)//如果i超限
            break;//弹出
        v[tos[j++].i]=true;//让当前军队到达当前根节点可到达的儿子,删去该子孙,找下一当前根节点可到达的儿子
        i++;//找下一军队
    }
    while(v[tos[j].i]&&j//如果当前根节点可到达的儿子已被删去
        j++;//看下一个
    if(j//如果根节点可到达的儿子没被完全控制
        return false;//返回当前解不成立
    return true;//否则返回当前解成立
}
int main()
{
    scanf("%d",&n);
    int ma=0;
    for(int i=1;iint a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
        ma=max(ma,c);//ma记最大边权
    }//读入
    bfs();//预处理
    scanf("%d",&m);//读入
    if(m//如果军队数小于根节点的儿子数
    {
        printf("-1");//无合法解
        return 0;
    }
    for(int i=1;i<=m;i++)
    {
        int a;
        scanf("%d",&a);
        ar[cnt++].i=a;
    }读入
    long long l=-1,r=10ll*(long long)ma*(long long)n;//(l,r)左开右开区间
    while(l1)
    {
        long long mid=l+((r-l)>>1);
        if(check(mid))
            r=mid;
        else
            l=mid;
    }//二分
    printf("%lld",r);//输出
    return 0;
}

要刷数论了…
最后的二分
٩(・ิ ・ิ๑)۶٩(・ิ ・ิ๑)۶*

你可能感兴趣的:(二分答案,贪心,作文系列)