1. 程式人生 > >洛谷P1084 疫情控制(NOIP2012)(二分答案,貪心,樹形DP)

洛谷P1084 疫情控制(NOIP2012)(二分答案,貪心,樹形DP)

難度 發現 水水的 ++ amp 貪心 reg fin 多說

洛谷題目傳送門

費了幾個小時杠掉此題,如果不是那水水的數據的話,跟天天愛跑步的難度真的是有得一比。。。

話說蒟蒻仔細翻了所有的題解,發現巨佬寫的都是倍增,復雜度是\(O(n\log n\log nw)\)的,貌似還不夠優秀。

其實我們與其對於每一個點都通過倍增向上找到對應位置,還不如直接從上到下dfs一遍,判斷:如果當前點子樹內初始位置最淺的軍隊與當前點距離不超過\(mid\),或者所有子樹都被封鎖,那麽當前點也被封鎖。

這樣以後再二分,時間復雜度降至\(O(n\log nw)\)。其它部分的思路Dalao們的題解裏都講清楚了,蒟蒻也不多說了qwq

#include<bits/stdc++.h>
#define LL long long
#define RG register
#define R RG int
using namespace std;
const int N=5e4+9,M=1e5+9;
int p,he[N],ne[M],to[M],w[M],top[N],at[N],t[N],mn[N];
LL mid,d[N],dis[N];
bool cov[N],use[N];
inline bool cmp(R x,R y){
    return d[x]>d[y];
}
void dp(R x,R f){
    top[x]=p;
    if(mid<d[x])mid=d[x];//控制二分上界
    if(to[he[x]]==f)he[x]=ne[he[x]];//悄悄把反邊刪掉
    for(R i=he[x];i;i=ne[i]){
        if(to[ne[i]]==f)ne[i]=ne[ne[i]];
        d[to[i]]=d[x]+w[i];
        dp(to[i],x);
    }
}
void dfs(R x){
    if(cov[x]){dis[x]=0;return;}
    dis[x]=1ll<<60;
    if(!(cov[x]=he[x]))return;//到達葉子節點還沒有被封鎖
    for(R y,i=he[x];i;i=ne[i]){
        dfs(y=to[i]);
        cov[x]&=cov[y];
        dis[x]=min(dis[x],dis[y]+w[i]);
    }
    if(dis[x]<=mid)cov[x]=1;//子樹內軍隊能夠趕到
}
int main(){
    R n,m,cnt=0,i,u,v;
    cin>>n;
    for(i=1;i<n;++i){
        cin>>u>>v>>w[++p];w[p+1]=w[p];
        ne[p]=he[u];to[he[u]=p]=v;++p;
        ne[p]=he[v];to[he[v]=p]=u;
    }
    for(i=he[1];i;i=ne[i])
        d[p=to[i]]=w[i],dp(t[++cnt]=to[i],1);
    cin>>m;
    if(cnt>m)return cout<<"-1"<<endl,0;//無解
    for(i=1;i<=m;++i)cin>>at[i];
    sort(t+1,t+cnt+1,cmp);//排序,方便接下來貪心
    sort(at+1,at+m+1,cmp);
    RG LL l=0,r=mid+d[t[1]];
    while(l<r){
        mid=(l+r)>>1;
        memset(cov,0,n+1);
        memset(use,0,n+1);
        for(i=1;i<=cnt;++i)mn[t[i]]=0;//清空
        for(i=1;d[at[i]]>mid;++i)
            cov[at[i]]=1;//到不了根節點,直接留在子樹內
        for(p=i;i<=m;++i)
            if(!mn[top[at[i]]])mn[top[at[i]]]=i;
            //每個子樹預留一個貢獻最小的軍隊
        use[0]=1;u=m+1;
        for(i=1;i<=cnt;++i){
            dfs(t[i]);
            if(cov[t[i]])continue;
            if(use[mn[t[i]]]){//預留已用,只好拿其它子樹的
                for(--u;u>=p&&(d[at[u]]+d[t[i]]>mid||use[u]);--u);
                if(u<p)break;
                use[u]=1;
            }
            else use[mn[t[i]]]=1;//預留直接用
        }
        u>=p?r=mid:l=mid+1;
    }
    cout<<l<<endl;
    return 0;
}

洛谷P1084 疫情控制(NOIP2012)(二分答案,貪心,樹形DP)