NOIP2012 疫情控制 (二分+倍增+贪心)

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

满分做法:

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

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

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

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

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\n",ans); 
  return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/lihan123/p/11656266.html
今日推荐