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

洛谷题目传送门

费了几个小时杠掉此题,如果不是那水水的数据的话,跟列队的难度真的是有得一比。。。

话说蒟蒻仔细翻了所有的题解,发现巨佬写的都是倍增,复杂度是\(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;
}
posted @ 2018-09-25 23:01  Flash_Hu  阅读(424)  评论(0编辑  收藏  举报