zoukankan      html  css  js  c++  java
  • NOIP 2012疫情控制 (二分+倍增+贪心)

    肝了一个下午,终于把这个绝世好题写完了(滑稽)

    满分做法:

    看到题目求最短时间,说明更高的时间也可以控制,满足答案单调性,可以二分;(技巧)

    看到这些比较复杂的题目,一定要滤清自己该干什么,不要慌!!!

    显然一个军队最后停留的节点深度越小,它控制的叶子结点越多。所以我们尽量让军队往上走,如果过程中它达到了时间限制,那么就把当前节点打上驻扎标记。

    若一个军队可以走到根节点,我们让他暂停。我们记录它到达根节点后剩余的时间,可以用二元组去存储。整个过程可以用倍增去解决。

    void dfs(int x)//倍增预处理
    {
     for(int p=last[x];p;p=pre[p])
     {
       int v=other[p];
       if(v==jump[x][0])
       continue;
       jump[v][0]=x;
       dis[v][0]=len[p];
       dfs(v);    
     }
    }
    bool sta[50007];//该节点是否驻扎 
    pair<ll,int> h[50007];//二元组记录上移后闲置军队,second为上移后的节点 ,first为剩余时间 
    
      int ctot=0; 
      for(int i=1;i<=m;i++)
      {
            int x=id[i];//军队位置
        ll cnt=0;//上移时间
        for(int j=19;j>=0;j--)//倍增 
        {
            if(jump[x][j]>1&&cnt+dis[x][j]<=limit)
            {
              cnt+=dis[x][j];
              x=jump[x][j];
            }
            }
        if(jump[x][0]==1&&dis[x][0]+cnt<=limit)
        {
          h[++ctot]=make_pair(limit-dis[x][0]-cnt,x);
        }
        else sta[x]=1;
      }

    接下来判断需要驻扎的节点(与根节点直接相连),直接向下dfs即可,如果遇到有驻扎的地方就返回1。如果到叶子结点时,叶子没有驻扎

    就返回0。

    bool dfs1(int x)
    {
      bool pson=0;//判断有没有儿子
      if(sta[x]) return 1;
      for(int p=last[x];p;p=pre[p])
      {
       int v=other[p];
       if(v==jump[x][0]) continue;
       pson=1;
       if(!dfs1(v))
       return 0;
      }
      if(!pson) return 0;//如果是叶子结点且无驻扎
      return 1; 
    }
    
    bool need[50007];//此节点需不需要驻扎
    for(int p=last[1];p;p=pre[p])
      {
       int v=other[p];
       if(!dfs1(v))
       need[v]=1;
      }

    之后,该对需要驻扎的节点进行初步处理。这里用到了贪心,对于任意一个需要被驻扎的节点,若它上面停留着一支军队不能到达根节点并返回该节点,

    则令其驻扎在该节点;另外的,因为一个节点只需要一支军队驻扎,因此我们在这里选择剩余时间最小的节点,对二元组排序即可。为什么呢?因为到达根节点

    后,可以归并为同一种状态,都是从根节点向子节点走剩余的时间。因为排序后,后面的点的剩余时间大于该点,但和此点到根节点边权的大小就不得而知。但是

    我可以用较小的剩余时间去驻扎,何乐而不为呢!

    sort(h+1,h+ctot+1);
      int atot=0;
      for(int i=1;i<=ctot;i++)
      {
        if(need[h[i].second]&&h[i].first<dis[h[i].second][0])////若该军队所处的节点需要被驻扎且该军队无法到达根节点并返回    
        need[h[i].second]=0;
        else tim[++atot]=h[i].first;
      }

    下一步找到仍需要驻扎的节点,直接储存边权即可

     //找到仍需驻扎的节点
      int btot=0;
      for(int p=last[1];p;p=pre[p])
      {
       int v=other[p];
       if(need[v])
       ned[++btot]=dis[v][0];//储存从根节点到它的时间    
      }
      

    最后就是贪心匹配最终结果,排序,对于现在闲置的军队和需要被驻扎的节点,让剩余时间小的军队优先驻扎在距离根节点近的节点,这样可以保证决策最优。

    if(atot<btot)//如果剩余的军队比需要被驻扎的节点还少,则不能成功 
      return 0;
      sort(tim+1,tim+atot+1);
      sort(ned+1,ned+btot+1);
      int h=1,h1=1;
      while(h<=atot&&h1<=btot)//贪心,用剩余时间少的去覆盖 
      {
       if(tim[h]>=ned[h1])
       {
            h1++;
            h++;
       }
       else h++;
      }
      if(h1>btot) return 1;
      else return 0;

    这样这道题就轻松解决了(ε=(´ο`*)))唉)

    附上完整代码

    #include<queue>
    #include<cstring>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    #include<iostream>
    using namespace std;
    const int maxm=100007;
    typedef long long ll;
    int n,m;
    int id[50007];
    int pre[maxm],last[50007],other[maxm],tot,len[maxm];
    int jump[50007][21];
    ll l,r,mid,ans;
    ll dis[50007][21];//倍增长度 
    bool sta[50007];//该节点是否驻扎 
    pair<ll,int> h[50007];//二元组记录上移后闲置军队,second为上移后的节点 ,first为剩余时间 
    ll tim[50007];//储存军队的剩余时间
    bool need[50007];
    ll ned[50007];
    void add(int x,int y,int z)
    {
     tot++;
     pre[tot]=last[x];
     last[x]=tot;
     other[tot]=y;
     len[tot]=z;    
    }
    void dfs(int x)
    {
     for(int p=last[x];p;p=pre[p])
     {
       int v=other[p];
       if(v==jump[x][0])
       continue;
       jump[v][0]=x;
       dis[v][0]=len[p];
       dfs(v);    
     }
    }
    bool dfs1(int x)
    {
      bool pson=0;
      if(sta[x]) return 1;
      for(int p=last[x];p;p=pre[p])
      {
       int v=other[p];
       if(v==jump[x][0]) continue;
       pson=1;
       if(!dfs1(v))
       return 0;
      }
      if(!pson) return 0;
      return 1; 
    }
    bool check(ll limit)
    {
      //上移 
      memset(sta,0,sizeof(sta));
      memset(tim,0,sizeof(tim));
      memset(ned,0,sizeof(ned));
      memset(h,0,sizeof(h));
      memset(need,0,sizeof(need));
      int ctot=0; 
      for(int i=1;i<=m;i++)
      {
        int x=id[i];
        ll cnt=0;//上移时间
        for(int j=19;j>=0;j--)//倍增 
        {
            if(jump[x][j]>1&&cnt+dis[x][j]<=limit)
            {
              cnt+=dis[x][j];
              x=jump[x][j];
            }
        }
        if(jump[x][0]==1&&dis[x][0]+cnt<=limit)
        {
          h[++ctot]=make_pair(limit-dis[x][0]-cnt,x);
        }
        else sta[x]=1;
      }
      //寻找需要驻扎的结点
      for(int p=last[1];p;p=pre[p])
      {
       int v=other[p];
       if(!dfs1(v))
       need[v]=1;
      }
      
      //对需要驻扎的进行初步处理
      sort(h+1,h+ctot+1);
      int atot=0;
      for(int i=1;i<=ctot;i++)
      {
        if(need[h[i].second]&&h[i].first<dis[h[i].second][0])////若该军队所处的节点需要被驻扎且该军队无法到达根节点并返回    
        need[h[i].second]=0;
        else tim[++atot]=h[i].first;
      }
      
      //找到仍需驻扎的节点
      int btot=0;
      for(int p=last[1];p;p=pre[p])
      {
       int v=other[p];
       if(need[v])
       ned[++btot]=dis[v][0];//储存从根节点到它的时间    
      }
      
      if(atot<btot)//如果剩余的军队比需要被驻扎的节点还少,则不能成功 
      return 0;
      sort(tim+1,tim+atot+1);
      sort(ned+1,ned+btot+1);
      int h=1,h1=1;
      while(h<=atot&&h1<=btot)//贪心,用剩余时间少的去覆盖 
      {
       if(tim[h]>=ned[h1])
       {
            h1++;
            h++;
       }
       else h++;
      }
      if(h1>btot) return 1;
      else return 0;
    }
    int main()
    {
      scanf("%d",&n);
      bool flag=0;
      for(int i=1;i<=n-1;i++)
      {
       int x,y,z;
       scanf("%d%d%d",&x,&y,&z);
       add(x,y,z);
       add(y,x,z);
       r+=z;
      }
      dfs(1);  
      for(int j=1;j<=19;j++)
      {
       for(int i=1;i<=n;i++)
       {
        jump[i][j]=jump[jump[i][j-1]][j-1];
        dis[i][j]=dis[i][j-1]+dis[jump[i][j-1]][j-1];
       }
      }
      scanf("%d",&m);
      for(int i=1;i<=m;i++)
      scanf("%d",id+i);
      ll l=0;
      while(l<=r)
      {
           mid=(l+r)>>1;
           if(check(mid))
           {
              ans=mid;
           r=mid-1;
           flag=1;    
           }
           else l=mid+1;
      }
      if(!flag)
      cout<<"-1"<<endl;
      else
      printf("%lld
    ",ans); 
      return 0;
    }
    View Code
  • 相关阅读:
    php 单双引号的区别
    SpringBoot动态代理使用Cglib还是jdk的问题
    SpringBoot MyBatis 一级缓存和二级的配置理解
    SpringBoot+MyBatis,显示SQL方式
    java lambda分页
    关于Spring的@Value注解使用Integer方式
    mysql死锁,等待资源,事务锁,Lock wait timeout exceeded; try restarting transaction解决
    关于Integer包装类对象之间值的比较
    你未必了解的DNS
    SpringCloudConfig报错Cannot clone or checkout repository:https://gitee.com/yanfa401/config-repo
  • 原文地址:https://www.cnblogs.com/lihan123/p/11656266.html
Copyright © 2011-2022 走看看