【NOIP2012提高】蓝书(算法竞赛进阶指南)刷题记录——疫情控制(二分+树上倍增+贪心)

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/83689111

题目:luogu1084.

题目大意:给定一棵树,以及一些在树上的军队.现在这些军队可以走动,并能在点上驻扎,从一条边的一段走到另一端需要与这条的长度等价的时间.现在要求用最短的时间,使得所有叶子节点到根节点的路径上有军队,且军队不能在根节点驻扎.

由于我们肯定更想让一个点覆盖的叶子节点更多,所以我们会让军队尽量往上走.

我们考虑暴力枚举,那么一个点能到哪个点需要靠时间枚举,所以我们先枚举时间.很显然时间越长能够覆盖的叶子就越多,所以答案是具有单调性的,这提示我们考虑用二分求解.

有了二分,我们就将问题转化为判定军队是否能在确定的时间内覆盖所有叶子了.

我们考虑用贪心来判定是否可以覆盖叶子节点.先将所有军队分为两类,一类是可以达到根的,一类是无法达到根的.对于后者,我们直接让他们尽量往上走;对于前者,我们考虑让他们之中剩余时间最短的覆盖距离根最近的儿子子树.

但是有一个问题,若有一支军队从一棵子树出发又回到了这棵子树,这个贪心貌似就不对了.

这个时候我们做一个处理,若一棵子树未被覆盖,且这棵子树的根上的军队无法走到根再返回,就直接让这只军队留在这棵子树上.

我们可以证明这样子做是对的:若这棵子树i上的军队s无法返回到这棵子树上,那么军队s必然会去覆盖其它子树i',这棵子树i就需要其它军队s'来覆盖这棵子树.若军队s'可以覆盖子树i,那么也必然可以覆盖子树i'.所以让军队s留在子树i必然不会比原来更劣.

注意,这样做的前提是这棵子树i还未被覆盖,我原来以为不管是否覆盖都可以这样做就一直过不去.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=50000,C=20;
const LL INF=(1LL<<50)-1;
struct side{
  int y,next;
  LL v;
}e[N*2+9];
int n,lin[N+9],top;
void ins(int x,int y,LL v){
  e[++top].y=y;e[top].v=v;
  e[top].next=lin[x];
  lin[x]=top;
}
//邻接表 
int gr[N+9][C];
LL dis[N+9][C],deep[N+9];
void dfs_dis(int k){
  for (int i=1;i<C;++i){
    gr[k][i]=gr[gr[k][i-1]][i-1];
    dis[k][i]=dis[k][i-1]+dis[gr[k][i-1]][i-1];
  }
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^gr[k][0]){
      gr[e[i].y][0]=k;
      deep[e[i].y]=deep[k]+e[i].v;
      dis[e[i].y][0]=e[i].v;
      dfs_dis(e[i].y);
    }
}
int ANC(int x,LL l){
  for (int i=C-1;i>=0;--i)
    if (gr[x][i]>1&&dis[x][i]<=l) l-=dis[x][i],x=gr[x][i];
  return x;
} 
//倍增预处理 
int m,fr[N+9],x[N+9],tx,f[N+9],tf,r[N+9];
bool use[N+9];
bool cmp(const int &a,const int &b){return deep[a]>deep[b];}
bool cmp1(const int &a,const int &b){return deep[a]<deep[b];}
void dfs(int k){
  if (use[k]) return;
  bool flag=1,son=0;
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^gr[k][0]){
      dfs(e[i].y);
      son=1;flag&=use[e[i].y];
    }
  use[k]|=flag&son;
}
//合并子树标记的dfs 
bool check(LL mid){
  tf=tx=0;
  for (int i=1;i<=n;++i) use[i]=0;
  for (int i=1;i<=m;++i) r[i]=ANC(f[i],mid);
  for (int i=1;i<=m;++i)
    if (deep[f[i]]>mid) use[r[i]]=1;
  for (int i=lin[1];i;i=e[i].next) dfs(e[i].y);
  for (int i=1;i<=m;++i)
    if (!use[r[i]]&&deep[f[i]]+deep[r[i]]>mid) use[r[i]]=1;
    else fr[++tf]=f[i];      //这里好像会出现两支军队都停留在这里却让剩余时间长的留在了这个点上的问题 
  //处理所有不跨越子树的军队 
  for (int i=lin[1];i;i=e[i].next)
    if (!use[e[i].y]) x[++tx]=e[i].y;
  sort(fr+1,fr+1+tf,cmp);
  sort(x+1,x+1+tx,cmp1);
  int j=1;
  for (int i=1;i<=tx;++i){
    while (deep[x[i]]+deep[fr[j]]>mid&&j<=tf) ++j;
    if (j>tf) return false;
    ++j;
  }
  return true;
  //处理剩余军队与子树 
}
LL ans;
Abigail into(){
  scanf("%d",&n);
  int x,y;LL v;
  for (int i=1;i<n;++i){
    scanf("%d%d%lld",&x,&y,&v);
    ins(x,y,v);ins(y,x,v);
  }
  scanf("%d",&m);
  for (int i=1;i<=m;++i) scanf("%d",&f[i]);
}
Abigail work(){
  dfs_dis(1);
  ans=INF;
  for (int i=49;i>=0;--i)
    if (check(ans-(1LL<<i))) ans-=1LL<<i;
}
Abigail outo(){
  printf("%lld\n",ans==INF?-1:ans);
}
int main(){
  into();
  work();
  outo();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/83689111