Luogu_P1084 疫情控制 树上倍增+贪心+二分

Luogu_P1084 疫情控制

### 树上倍增+贪心+二分

题目链接
首先越到根节点越优很显然
所有向上的问题就可以用树上倍增来优化
而且答案具有单调性
显然你ans的时间能完成ans+1也可以
那么就可以二分答案ans,是最大值最小
如何验证这个二分的答案???
首先把所有的点都上移到1号节点的子节点上
如果移动不到的话就地驻扎
这时候就需要一个新的贪心就是

如果一个军队的剩余时间不足以让他从走到根节点再走回来
那么他最优的操作就是不动

因为假如他移动过了根节点他也只能到距离小于他到根节点的距离的点上
有点绕嘴
而且需要一个新的点来覆盖他的子树
假如有点能覆盖他的子树
证明内个点的剩余时间比当前点的剩余时间长
可以有更多选择
什么傻子证明
剩下的代码注释里有


代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50010;
bool b=0,stad[maxn],nd[maxn];
int dep[maxn],n,head[maxn],tot,m;
int fa[maxn][35],t;
ll sum=0,a[maxn],dist[maxn][35],tim[maxn],ndd[maxn];
struct node{
    int nxt,to,dis;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
    #define dis(x) e[x].dis
}e[maxn<<1];
queue<int> q;
pair<ll,int> pr[maxn];
inline void add(int from,int to,int w){
    to(++tot)=to;dis(tot)=w;
    nxt(tot)=head[from];head[from]=tot;
}
//预处理
inline void bfs(){
    q.push(1);dep[1]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i);
            if(dep[y]) continue;
            dep[y]=dep[x]+1;
            fa[y][0]=x;dist[y][0]=dis(i);
            for(int j=1;j<=t;j++){
                fa[y][j]=fa[fa[y][j-1]][j-1];
                dist[y][j]=dist[y][j-1]+dist[fa[y][j-1]][j-1];
            }
            q.push(y);
        }
    }
}
//是否要驻扎
bool dfs(int x){
    bool ps=0;
    if(stad[x]) return 1;
    for(int i=head[x];i;i=nxt(i)){
        int y=to(i);
        if(dep[y]<dep[x]) continue;
        ps=1;
        if(!dfs(y)) return 0;
    }
    if(!ps) return 0;
    return 1;
}
inline bool ch(ll tili){
    memset(pr,0,sizeof(pr));
    memset(stad,0,sizeof(stad));
    memset(nd,0,sizeof(nd));
    memset(tim,0,sizeof(tim));
    int prcnt=0,tmcnt=0,ncnt=0;
    for(int i=1;i<=m;i++){
        ll x=a[i],cnt=0;
        for(int j=t;j>=0;j--)
            if(fa[x][j]>1 && cnt+dist[x][j]<=tili){
                cnt+=dist[x][j];x=fa[x][j];
            }
        if(fa[x][0]==1 && cnt+dist[x][0]<=tili)
            pr[++prcnt]=make_pair(tili-cnt-dist[x][0],x);
        else
            stad[x]=1;//就地驻扎
    }//找到空闲的,也就是可以到根节点还有剩余时间的
    for(int i=head[1];i;i=nxt(i))
        if(!dfs(to(i))) nd[to(i)]=1;//需要被覆盖的1的子节点
    sort(pr+1,pr+1+prcnt);
    for(int i=1;i<=prcnt;i++)
        if(nd[pr[i].second] && pr[i].first<dist[pr[i].second][0])//如果不能通过根节点再回来就不能剩余
            nd[pr[i].second]=0;//就地驻扎,就不需要别的点去覆盖
        else tim[++tmcnt]=pr[i].first;//剩余的空余军队多出来的时间数
    for(int i=head[1];i;i=nxt(i))
        if(nd[to(i)]) ndd[++ncnt]=dist[to(i)][0];//需要被覆盖的点的路径边权
    if(tmcnt<ncnt) return 0;//空余军队比需要覆盖的点少,return 0
    sort(tim+1,tim+1+tmcnt);sort(ndd+1,ndd+1+ncnt);
    int i=1,j=1;
    while(i<=ncnt && j<=tmcnt){
        if(tim[j]>=ndd[i]){//到时间之前可以覆盖住两个都向后扫
            i++,j++;
        }else j++;//覆盖不住只能换时间更长的军队
    }
    if(i>ncnt) return 1;//全部扫完了
    else return 0;
}
int main()
{
    scanf("%d",&n);
    for(int x,y,z,i=1;i<n;i++)
        scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z),sum+=z;
    scanf("%d",&m);t=log2(n)+1;
    for(int i=1;i<=m;i++) scanf("%lld",&a[i]);
    bfs();
    ll l=0,r=sum+1,ans=0;
    while(l<=r){
        ll mid=(l+r)>>1;
        if(ch(mid)){
            r=mid-1;ans=mid;b=1;
        }else l=mid+1;
    }//二分需要的最大时间,最大值最小
    if(b) printf("%lld\n",ans);
    else printf("-1\n");
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ChrisKKK/p/11635138.html