NOIP 2012 疫情控制

题目描述

H 国有 n个城市,这 n个城市用n−1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。

H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。

现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。

请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

输出格式:

一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出−1

对于 100%的数据,2≤m≤n≤50,000,0<w <10^9

Solution:

一眼看过去,是一个最小化最大值的问题,所以可以考虑二分答案?

单调性显然。

那么问题就是,我们现在有了一个上限时间mid,军队必须默契配合,在mid时间内控制整个国家。

怎么默契配合呢???
发现一个关键点,军队要控制的本质是一个子树的所有叶子。

那么,一个军队如果往上走,一定不会控制地更少,只可能控制地更多!!!

所以,军队只要疯狂的尽可能地往上走,那么就可能是这个军队在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好了。

 

posted @ 2018-08-27 23:12  *Miracle*  阅读(272)  评论(0编辑  收藏  举报