zoukankan      html  css  js  c++  java
  • 【CodeForces】671 D. Roads in Yusland

    【题目】D. Roads in Yusland

    【题意】给定n个点的树,m条从下往上的链,每条链代价ci,求最少代价使得链覆盖所有边。n,m<=3*10^5,ci<=10^9,time=4s。

    【算法】树形DP+线段树||可并堆

    【题解】从每条边都需要一条链来覆盖的角度出发,令f[i]表示覆盖子树 i 以及 i到fa[i]的边(i->fa[i])的最小代价,整个过程通过dfs从下往上做。

    由于f[son[i]]已知,所以f[i]的转移实际上是考虑覆盖i->fa[i]的链,定义这条链为主链。那么f[i]=min(c+Σf[k]),c是主链代价,k是主链上在i子树内的所有点的子节点(不含主链上点),所有起点在子树i内终点在i的祖先的链都可以作为主链,取最小值

    自然地,可以在递归的过程中将Σf[k]并入c中。具体而言,对于每个点x:

    1.删。将终点在x的链删除。

    2.加。记sum=Σf[son[i]],son[i]子树内所有的链c+=sum-f[son[i]](就是把Σf[k]并入c中),特别地,起点在i的链c+=sum。

    3.取。f[i]是子树i中所有的链c的最小值。

    现在需要快速支持子树加值和子树求最小值的操作,可以用线段树按dfs序维护所有链实现(把链按起点的dfs序作为线段树下标)。

    复杂度O(n log n)。

    #include<cstdio>
    #include<cctype>
    #include<vector>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int read(){
        char c;int s=0,t=1;
        while(!isdigit(c=getchar()))if(c=='-')t=-1;
        do{s=s*10+c-'0';}while(isdigit(c=getchar()));
        return s*t;
    }
    const int maxn=300010;
    const ll inf=1e15;
    struct tree{int l,r;ll delta,mins;}t[maxn*4];
    struct edge{int v,from;}e[maxn*2];
    vector<int>v[maxn];
    int n,m,ku[maxn],kv[maxn],kw[maxn],kp[maxn],tot=0,dfsnum=0,first[maxn],be[maxn],ed[maxn];
    ll a[maxn],f[maxn];
    void ins(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    void dfs_order(int x,int fa){
        be[x]=dfsnum+1;
        for(int i=0;i<(int)v[x].size();i++){
            kp[v[x][i]]=++dfsnum;
            a[dfsnum]=kw[v[x][i]];
        }
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
            dfs_order(e[i].v,x);
        }
        ed[x]=dfsnum;
        if(be[x]>ed[x]){printf("-1");exit(0);}
    }
    void modify(int k,ll x){t[k].mins+=x;t[k].delta+=x;}
    void up(int k){t[k].mins=min(t[k<<1].mins,t[k<<1|1].mins);}
    void down(int k){
        if(t[k].delta){
            modify(k<<1,t[k].delta);
            modify(k<<1|1,t[k].delta);
            t[k].delta=0;
        }
    }
    void build(int k,int l,int r){
        t[k].l=l;t[k].r=r;t[k].delta=0;
        if(l==r){t[k].mins=a[l];}else{
            int mid=(l+r)>>1;
            build(k<<1,l,mid);
            build(k<<1|1,mid+1,r);
            up(k);
        }
    }
    void add(int k,int l,int r,ll x){
        if(l<=t[k].l&&t[k].r<=r){modify(k,x);return;}
        down(k);
        int mid=(t[k].l+t[k].r)>>1;
        if(l<=mid)add(k<<1,l,r,x);
        if(r>mid)add(k<<1|1,l,r,x);
        up(k);
    }
    ll ask(int k,int l,int r){
        if(l<=t[k].l&&t[k].r<=r){return t[k].mins;}
        down(k);
        int mid=(t[k].l+t[k].r)>>1;
        ll ans=inf;
        if(l<=mid)ans=ask(k<<1,l,r);
        if(r>mid)ans=min(ans,ask(k<<1|1,l,r));
        return ans;
    }
    ll dp(int x,int fa){
        f[x]=0;ll sum=0;
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)sum+=dp(e[i].v,x);
        for(int i=0;i<(int)v[x].size();i++)add(1,v[x][i],v[x][i],inf);
        add(1,be[x],ed[x],sum);
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
            add(1,be[e[i].v],ed[e[i].v],-f[e[i].v]);
        }
        f[x]=ask(1,be[x],ed[x]);
        if(x!=1&&f[x]>=inf){printf("-1");exit(0);}
        return f[x];
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<n;i++){
            int u=read(),v=read();
            ins(u,v);ins(v,u);
        }
        for(int i=1;i<=m;i++){
            ku[i]=read(),kv[i]=read(),kw[i]=read();
            v[ku[i]].push_back(i);
        }
        dfsnum=0;
        dfs_order(1,0);
        build(1,1,dfsnum);
        for(int i=1;i<=m;i++)v[ku[i]].clear();
        for(int i=1;i<=m;i++)v[kv[i]].push_back(kp[i]);
        dp(1,0);
        ll ans=0;
        for(int i=first[1];i;i=e[i].from)ans+=f[e[i].v];
        printf("%lld",ans);
        return 0;
    }
    View Code

    可并堆写法:

    核心思想仍是——每条边都需要一条链来覆盖。

    整个过程通过dfs从下往上做,对于每个点x,维护一个堆包含所有起点在子树x内终点为x的祖先的链(按价值从小到大)。

    维护的过程只需要将所有儿子的堆合并过来,然后删除终点在x的链。(堆的删除不需要真的删除,只需要在调用堆顶是判断是否已被删除)

    接下来考虑选用哪些链,考虑点x时,子树x内所有边都已经被覆盖,所以实际上是在考虑x->fa[x]这条边的覆盖,那么此时堆x中的链都可以随意选用,但是选用哪条对未来更优当前并不知道。

    采用反悔的思想,先选用代价w最小的链,并将堆整体标记-w,之后考虑选用其它边实际上就是“更换”的操作了,当然选用的代价w的链不移除(在堆中代价为0)将一直发挥作用直至其终点。

    这样做的正确性就在于,在标记-w时,堆中所有的链可以随意换用,因为都会影响x->fa[x]而子树x已经完全覆盖了无须考虑。

    总结起来,对于点x:

    1.合并所有son[x]。

    2.找到堆顶w加入答案。(不需要特别做删除)

    3.整体标记-w。

    复杂度O(n log n),常数优势明显。

    #include<cstdio>
    #include<cctype>
    #include<cctype>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int read(){
        char c;int s=0,t=1;
        while(!isdigit(c=getchar()))if(c=='-')t=-1;
        do{s=s*10+c-'0';}while(isdigit(c=getchar()));
        return s*t;
    }
    const int maxn=300010;
    struct edge{int v,from;}e[maxn*2];
    int tot,first[maxn],l[maxn],r[maxn],d[maxn],root[maxn],n,m,top[maxn];
    ll delta[maxn],w[maxn],ans=0;
    bool vis[maxn];
    
    void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    void modify(int k,int x){delta[k]+=x;w[k]+=x;}
    void down(int x){
        if(delta[x]){
            if(l[x])modify(l[x],delta[x]);
            if(r[x])modify(r[x],delta[x]);//make 0 no influence!
            delta[x]=0;
        }
    }
    int merge(int x,int y){
        if(!x||!y)return x^y;
        if(w[x]>w[y])swap(x,y);
        down(x);r[x]=merge(r[x],y);
        if(d[l[x]]<d[r[x]])swap(l[x],r[x]);
        d[x]=d[r[x]]+1;
        return x;
    }
    void dfs(int x,int fa){
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)dfs(e[i].v,x),root[x]=merge(root[x],root[e[i].v]);
        vis[x]=1;
        if(x==1)return;
        while(vis[top[root[x]]])root[x]=merge(l[root[x]],r[root[x]]);
        if(!root[x]){printf("-1");exit(0);}
        ans+=w[root[x]];modify(root[x],-w[root[x]]);
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<n;i++){
            int u=read(),v=read();
            insert(u,v);insert(v,u);
        }
        for(int i=1;i<=m;i++){
            int u=read();top[i]=read();w[i]=read();
            root[u]=merge(root[u],i);
        }
        ans=0;
        dfs(1,0);
        printf("%lld",ans);
        return 0;
    }
    View Code
  • 相关阅读:
    关于字符编码,你所需要知道的(ASCII,Unicode,Utf-8,GB2312…)
    Python基础一
    windows环境下 安装python2和python3
    Python数据类型及常用操作
    Python流程控制
    Python用户交互以及数据类型
    Python介绍以及Python环境搭建
    multiprocessor(下)
    multiprocessor(中)
    multiprocess(上)
  • 原文地址:https://www.cnblogs.com/onioncyc/p/8085283.html
Copyright © 2011-2022 走看看