NOIP 2012 疫情控制

題目描述

H 國有 n個城市,這 n個城市用n−1條雙向道路相互連通構成一棵樹,1號城市是首都,也是樹中的根節點。node

H國的首都爆發了一種危害性極高的傳染病。當局爲了控制疫情,不讓疫情擴散到邊境城市(葉子節點所表示的城市),決定動用軍隊在一些城市創建檢查點,使得從首都到邊境城市的每一條路徑上都至少有一個檢查點,邊境城市也能夠創建檢查點。但特別要注意的是,首都是不能創建檢查點的。c++

如今,在 H 國的一些城市中已經駐紮有軍隊,且一個城市能夠駐紮多個軍隊。一支軍隊能夠在有道路鏈接的城市間移動,並在除首都之外的任意一個城市創建檢查點,且只能在一個城市創建檢查點。一支軍隊通過一條道路從一個城市移動到另外一個城市所須要的時間等於道路的長度(單位:小時)。spa

請問最少須要多少個小時才能控制疫情。注意:不一樣的軍隊能夠同時移動。code

輸出格式:

一個整數,表示控制疫情所須要的最少時間。若是沒法控制疫情則輸出−1blog

對於 100%的數據,2≤m≤n≤50,000,0<w <10^9排序

Solution:

一眼看過去,是一個最小化最大值的問題,因此能夠考慮二分答案?ip

單調性顯然。it

那麼問題就是,咱們如今有了一個上限時間mid,軍隊必須默契配合,在mid時間內控制整個國家。io

怎麼默契配合呢???
發現一個關鍵點,軍隊要控制的本質是一個子樹的全部葉子。
class

那麼,一個軍隊若是往上走,必定不會控制地更少,只可能控制地更多!!!

因此,軍隊只要瘋狂的儘量地往上走,那麼就多是這個軍隊在mid內的最優策略了!!

 

可是棘手的問題是,一個軍隊可能會跨過首都進入另外一棵子樹。

可是顯然的,進入另外一棵子樹,必定就在這個子樹的根部呆着不動了。

先採用兩個倍增,mlogn找到每一個軍隊向上最多能到哪裏(欽定不能到根)。

把這個點標記封鎖。dfs一遍處理封鎖標記的上傳。

 

把停留在根的兒子的軍隊加入一個vector

vector按照剩餘時間排序,

若是須要封鎖x兒子,那麼找一個剩餘距離最小的軍隊,乾脆就呆在x好了。

剩下的、能到根的軍隊的剩餘時間加入一個set

 

暴力枚舉根的每一個沒有封鎖的兒子,把第一個大於這個邊權的剩餘時間(lower_bound一下)匹配上。

找不到第一個大於這個邊權的時間就return false

 

誒?這個樣子有80分。

爲何沒有AC?

 

由於還有一種狀況。

以前咱們說,「若是須要封鎖x兒子,那麼找一個剩餘距離最小的軍隊,乾脆就呆在x好了。

可是,若是x只有這一個軍隊,而且這個軍隊還能走很遠。可是就卡死在這一步了。

其實,假如x到根的路徑不是很長,可讓x出去,填到y,再把子樹z的某個軍隊出來,填到x

多是更優的!!

 

因此,腦殘地,作兩遍,一次是上面說的判斷方法,。

還有一次,把全部的根的兒子節點,能到根都到根,再依次考慮下去。

 

可是顯然錯誤。

由於, 第一次沒有考慮跨子樹,第二次所有跨子樹了><

可是noip數據太水,AC了~~~~~~~~~

(注意倍增的順序!!,先路程加上dis[to][j],再跳過去to=fa[to][j])

 

#include<bits/stdc++.h>
using namespace std; typedef long long ll; const int N=50000+5; int n; int m; int st[N]; struct node{ int nxt,to; ll val; }e[2*N]; int hd[N],cnt; ll l,r; bool son[N]; void add(int x,int y,ll z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt; } int fa[N][22]; ll dis[N][22]; ll ans=-1; bool exi[N]; bool tmp[N]; void dfs(int x){ for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa[x][0]) continue; fa[y][0]=x; dis[y][0]=e[i].val; if(x==1) son[y]=1; dfs(y); } } void dfs2(int x){ if(exi[x]) return; bool fl=true; bool has=false; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa[x][0]) continue; has=true; if(!exi[y]){ dfs2(y); if(!exi[y]) fl=false; } } if(!has) fl=false; if(fl) exi[x]=1; } struct po{ int pos; ll re; };//in the son of root
bool cmp(po a,po b){ return a.re<b.re; } vector<po>mem; multiset<ll>rt; multiset<ll>::iterator it; bool che(ll mid){ //cout<<endl<<"bug "<<mid<<endl;
    bool orz1=true; bool orz2=true; memset(exi,0,sizeof exi); mem.clear(); rt.clear(); for(int i=1;i<=m;i++){ int to=st[i]; ll has=0; for(int j=19;j>=0;j--){ if(fa[to][j]&&fa[to][j]!=1&&has+dis[to][j]<=mid){ has+=dis[to][j]; to=fa[to][j];//1.倍增的順序不能反了!! 
 } } //cout<<i<<" st "<<st[i]<<" : "<<to<<" "<<has<<endl;
        
        if(son[to]){ po ko; ko.pos=to,ko.re=mid-has; mem.push_back(ko); } else{//warning !! : no son of tree exi[] is true
            exi[to]=1; } } dfs2(1);  sort(mem.begin(),mem.end(),cmp); memcpy(tmp,exi,sizeof exi); for(int i=0;i<mem.size();i++){ po x=mem[i]; if(!exi[x.pos]){ exi[x.pos]=1; } else{ x.re-=dis[x.pos][0]; if(x.re>0){ rt.insert(x.re); } } } for(int i=hd[1];i;i=e[i].nxt){ int y=e[i].to; if(!exi[y]){ it=rt.lower_bound(e[i].val); if(it==rt.end()) { orz1=false;break;} rt.erase(it); } } if(orz1) return true; rt.clear(); memcpy(exi,tmp,sizeof tmp); for(int i=0;i<mem.size();i++){ po x=mem[i]; x.re-=dis[x.pos][0]; //cout<<x.pos<<" "<<x.re<<endl;
        if(x.re>0){ rt.insert(x.re); } else exi[x.pos]=1; } for(int i=hd[1];i;i=e[i].nxt){ int y=e[i].to; if(!exi[y]){ //cout<<" go "<<y<<endl;
            it=rt.lower_bound(e[i].val); if(it==rt.end()){ orz2=false;break;} rt.erase(it); } } if(orz2) return true; return false; } int main() { scanf("%d",&n); int x,y; ll z; for(int i=1;i<=n-1;i++){ scanf("%d%d%lld",&x,&y,&z); add(x,y,z);add(y,x,z); r+=z; } scanf("%d",&m); for(int i=1;i<=m;i++) scanf("%d",&st[i]); dfs(1); //cout<<" on the tree"<<endl;
    /*for(int i=1;i<=n;i++){ cout<<i<<" : "<<fa[i][0]<<" "<<dis[i][0]<<endl; }*/
    for(int j=1;j<=19;j++){ for(int i=1;i<=n;i++){ fa[i][j]=fa[fa[i][j-1]][j-1]; dis[i][j]=dis[i][j-1]+dis[fa[i][j-1]][j-1]; } } l=0; //printf("ahhahahaah %d\n",che(30));
    
    while(l<=r){ ll mid=(l+r)>>1; if(che(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%lld",ans); return 0; }

 

正解是這樣判斷的。

仍是全部的兒子處軍隊能到根的到根,不能到根的就exi[x]=1設立檢查站

對於在根的,

而後對於全部  根到未封鎖兒子的 邊權,從大到小排序。

對於x子樹,看根部是否存在一個軍隊,原來是從x出來的,而且是當前剩餘距離最小的。

若是有,那麼就反悔,讓他不要到根好了。

正確性比較顯然,首先必須原來是x裏的,其次,是當前最不能走的一個,讓它回去必定不劣,不然用一個能走的更遠的封鎖,可能不優。

 

不然,就讓大於x邊權的最小軍隊封鎖x好了。

相關文章
相關標籤/搜索