1. 程式人生 > 實用技巧 >洛谷 P1084 [NOIP2012 提高組] 疫情控制

洛谷 P1084 [NOIP2012 提高組] 疫情控制

題目連結: P1084 [NOIP2012 提高組] 疫情控制
題目大意:
有一個大小為 \(n\) 的樹形國家,首都為節點1,其首都出現了一種傳染病 COVID-19,為了防止疾病傳播到邊境,國家派出 \(m\) 個軍隊部署防疫站,初始每個軍隊所在城市為 \(P_i\) ,要求軍隊移動到一些城市,滿足在首都到任意一個邊境城市的路徑上都至少有一個防疫站(其中首都不能建防疫站),求所需的最短時間。
\(1\leq n,m\leq 50000\) ,(我也不知道咋轉換題意了,就這樣吧)
思路:
想一想就可以發現軍隊肯定往根節點走是最優的,由於答案具有單調性,考慮二分答案 \(mid\) ,對於走不到根節點的軍隊,我們儘量讓他們往上走即可,主要關注可以走到首都的部隊,他們若要控制道路那麼肯定都是部署在首都周圍的幾個城市裡,我們拿一個集合表示所有靠走不到根節點的軍隊無法全部覆蓋的周邊城市,這時我們還剩有能力跨越根節點的軍隊可以使用,這時有一個貪心策略:
根據離根節點距離從大到小列舉的每個兒子(從小到大也可以,內部的順序也換一下即可),如果子樹中有軍隊可用,就選最遠的部署在自己這裡,如果沒有,便選所有部隊中離根節點最近且還沒被使用的拿過來(更需要這些軍隊的城市之前已經處理過了)。
最終如果可以覆蓋全國,便把 \(mid\)

向下二分,vice versa.
很多題解在將軍隊往上跳的時候採用的是倍增的方法,是 \(O(nlognlognz)\) 的(\(z\) 為邊權),仔細思考可以發現我們只需要知道節點覆蓋情況,在 \(dfs\) 的過程中維護最近的軍隊即可,這樣時間複雜度 \(O(nlognz)\) ,可以吊打網上大多數程式碼(我的開O2是洛谷rank12,數人頭rank4)

Code:
不知道為什麼這道題寫的我身心俱疲,可能狀態不好吧。

#include<iostream>
#include<algorithm>
#include<cstring>
#define N 50100
#define ll long long
#define Inf 0x3f3f3f3f3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int head[N],to[N*2],nxt[N*2];
bool ocp[N],used[N];
ll cnt,len[N*2],dis[N],lim; 
int city[N],son[N],from[N],mlt[N];
int n,m;
void init(){
    mem(head,-1), cnt=-1;
}
void add_e(int a,int b,ll l,bool id){
    nxt[++cnt]=head[a];
    head[a]=cnt;
    to[cnt]=b;
    len[cnt]=l;
    if(id)add_e(b,a,l,0);
}
void dfs1(int x,int fa){
    if(fa==1)from[x]=x;
    else from[x]=from[fa];
    lim=max(lim,dis[x]);
    if(to[head[x]]==fa)head[x]=nxt[head[x]];
    for(int i=head[x];~i;i=nxt[i]){
        if(to[i]==fa)continue;
        dis[to[i]]=dis[x]+len[i];
        dfs1(to[i],x);
    }
}
ll dfs2(int x,int fa,int mid){
    if(ocp[x])return 0;
    if(head[x]==-1)return Inf;
    ll up=Inf;
    ocp[x]=true;
    for(int i=head[x];~i;i=nxt[i]){
        if(to[i]==fa)continue;
        up=min(up,len[i]+dfs2(to[i],x,mid));
        ocp[x]&=ocp[to[i]];
    }
    ocp[x]|=(up<=mid);
    return up;
}
bool cmp(int a,int b){
    return dis[a]>dis[b];
}
bool check(int mid){
    mem(ocp,false);
    mem(mlt,-1);
    mem(used,false);
    int i,p,u;
    for(i=0;i<m&&dis[city[i]]>mid;i++)
        ocp[city[i]]=true;
    dfs2(1,0,mid);
    for(p=i;i<m;i++)
        if(mlt[from[city[i]]]==-1)mlt[from[city[i]]]=i;
    u=m;
    for(i=0;i<cnt;i++){
        int c=son[i];
        if(ocp[c])continue;
        if(mlt[c]==-1||used[mlt[c]]){
            for(--u;u>=p&&(dis[city[u]]+dis[c]>mid||used[u]);u--);
            if(u<p)break;
            used[u]=true;
        }
        else used[mlt[c]]=true;
    }
    return u>=p;
}
int main(){
    ios::sync_with_stdio(false);
    int u,v,w;
    cin>>n;
    init();
    for(int i=1;i<n;i++){
        cin>>u>>v>>w;
        add_e(u,v,w,1);
    }
    cin>>m;
    for(int i=0;i<m;i++)cin>>city[i];
    dfs1(1,0);
    cnt=0;
    for(int i=head[1];~i;i=nxt[i]) son[cnt++]=to[i];
    if(cnt>m)return cout<<"-1\n",0;
    sort(city,city+m,cmp);
    sort(son,son+cnt,cmp);
    ll r=lim+dis[son[0]],l=0,mid;
    while(l<r){
        mid=(l+r)>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l<<endl;
    return 0;
}