zoukankan      html  css  js  c++  java
  • [学习笔记]最小树形图

    处理这样一类问题:

    给一个有向图,定义树形图:一个有向图以x为根的树形图,是一个n-1条边的集合,使得x能到达其他每一个点

    树形图的权值定义为边的和

    朱刘算法就是求最小树形图

     

    方法:

    1.给每个点p找一个边权最小的连向它的边,边权为val,前驱设为pre。找到了一个边集E0

    ans记录总权值

    2.检查,如果有一个孤立点(除了rt),那么无解,退出

    如果没有有向环,那么完毕。当前的ans就是答案。退出

    3.对于每一个有向环,缩点

    4.更新新图的权值:枚举所有的边,如果x,y缩点之后不是一个点,那么e[i].v-=val[y],象征,如果再选择这个点,那么就把环上的这个边删掉。

    重复以上过程,直到退出

    (5.如果要输出方案,那么在没有有向环之后,要把缩的点再展开大概用边来记录替换的信息,递归处理就可以吧,,,)

     

    复杂度:O(nm)可能远远不到这个上界

    正确性:由于有反悔操作,所以直接贪心就正确了。

    代码:

    poj3164 Command Network

    不能用快读,否则会TLE。。。辣鸡poj

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #define reg register int
    #define il inline
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    il void rd(int &x){
        char ch;x=0;bool fl=false;
        while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
        (fl==true)&&(x=-x);
    }
    namespace Miracle{
    const int N=105;
    const int M=1e5+5;
    const int inf=2000000000;
    struct node{
        int x,y;
        double v;
    }e[M];
    int id[N],vis[N],pre[N];
    double val[N];
    double px[N],py[N];
    int n,m;
    double ans;
    int cnt;
    double dist(int a,int b){
        return sqrt((double)(px[a]-px[b])*(px[a]-px[b])+(double)(py[a]-py[b])*(py[a]-py[b]));
    }
    double wrk(int rt,int n){
        double ret=0;
    //    printf("%.10lf",val[0]);
        int turn=0;
        while(1){
            ++turn;
        //    cout<<" turn "<<turn<<" "<<n<<" rt "<<rt<<endl;
            for(reg i=0;i<n;i++) val[i]=inf;
            for(reg i=0;i<m;++i){
                if(e[i].x!=e[i].y){
                    if(val[e[i].y]>e[i].v){
                        val[e[i].y]=e[i].v;
                        pre[e[i].y]=e[i].x;
                    }
                }
            }
            for(reg i=0;i<n;++i){
                //cout<<i<<" : "<<pre[i]<<" : "<<val[i]<<endl;
                if(i==rt) continue;
                if(val[i]==inf) return -1;
            }
            cnt=0;
            memset(id,-1,sizeof id);
            memset(vis,-1,sizeof vis);
            val[rt]=0.00;
            for(reg i=0;i<n;++i){
                //cout<<" start "<<i<<endl;
                ret+=val[i];
                int v=i;
                while(vis[v]!=i&&id[v]==-1&&v!=rt){
                    vis[v]=i;
                    v=pre[v];
                    //cout<<" vv "<<v<<endl;
                }
                if(v!=rt&&id[v]==-1){
                    //cout<<" new "<<endl;
                    for(reg u=pre[v];u!=v;u=pre[u]) id[u]=cnt;
                    id[v]=cnt++;
                }
            }
            if(cnt==0) break;
        //    cout<<" after dfs "<<cnt<<endl;
            for(reg i=0;i<n;++i) if(id[i]==-1) id[i]=cnt++;
            //for(reg i=1;i<=n;++i) cout<<i<<" -- "<<id[i]<<endl;
            for(reg i=0;i<m;++i){
                int x=e[i].x;
                int y=e[i].y;
                e[i].x=id[x];
                e[i].y=id[y];
                if(id[x]!=id[y]) e[i].v-=val[y];
            }
            //cout<<" endneenndd "<<ret<<" cnt "<<cnt<<endl;
            n=cnt;
            rt=id[rt];
        }
        return ret;
    }
    int main(){
        while(scanf("%d%d",&n,&m)!=EOF){
            for(reg i=0;i<n;i++){
                scanf("%lf%lf",&px[i],&py[i]);
            }
            for(reg i=0;i<m;++i){
                scanf("%d%d",&e[i].x,&e[i].y);
                --e[i].x;
                --e[i].y;
                //if(x==y) continue;
                if(e[i].x!=e[i].y)e[i].v=dist(e[i].x,e[i].y);
                else e[i].v=inf;
            //    cout<<" x y z "<<x<<" "<<y<<" "<<e[lp].v<<endl;
            }
            ans=wrk(0,n);
            if(ans==-1){
                printf("poor snoopy
    ");
            }else{
                printf("%.2f
    ",ans);
            }
        }
        return 0;
    }
    
    }
    signed main(){
    //    freopen("data.in","r",stdin);
    //    freopen("my.out","w",stdout);
        Miracle::main();
        return 0;
    }
    
    /*
       Author: *Miracle*
       Date: 2019/1/7 17:25:32
    */
    View Code

    或者可以用dfs判环,类似tarjan

    复杂度一定有保证:

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #define reg register int
    #define il inline
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    il void rd(int &x){
        char ch;x=0;bool fl=false;
        while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
        (fl==true)&&(x=-x);
    }
    namespace Miracle{
    const int N=105;
    const int M=1e4+5;
    struct node{
        int x,y;
        double v;
    }e[M];
    int id[N],vis[N],pre[N];
    bool in[N];
    double val[N];
    double px[N],py[N];
    int n,m;
    double ans;
    int sta[N],top;
    int cnt;
    double dist(int a,int b){
        return sqrt((double)(px[a]-px[b])*(px[a]-px[b])+(double)(py[a]-py[b])*(py[a]-py[b]));
    }
    void dfs(int x){
    //    cout<<" x "<<x<<endl;
        sta[++top]=x;in[x]=1;
        vis[x]=1;
        if(pre[x]==1) return;
        else if(!vis[pre[x]]) dfs(pre[x]);
        else if(in[pre[x]]){
        //    cout<<" new huan "<<endl;
            int z;
            ++cnt;
            do{
                z=sta[top];
                id[z]=cnt;
                in[z]=0;
                top--;
            }while(z!=pre[x]);
        }
    }
    double wrk(int n){
        double ret=0;
        int rt=1;
        memset(val,0x7f,sizeof val);
        int turn=0;
        while(1){
            ++turn;
        //    cout<<" turn "<<turn<<" "<<n<<endl;
            memset(vis,0,sizeof vis);
            memset(id,0,sizeof id);
            memset(in,0,sizeof in);
            memset(pre,0,sizeof pre);
            memset(val,0x7f,sizeof val);
            top=0;cnt=1;
            id[1]=cnt;
            for(reg i=1;i<=m;++i){
                if(e[i].x!=e[i].y&&e[i].y!=rt){
                    if(val[e[i].y]>e[i].v){
                        val[e[i].y]=e[i].v;
                        pre[e[i].y]=e[i].x;
                    }
                }
            }
    //        for(reg i=1;i<=n;++i){
    //            cout<<i<<" : "<<pre[i]<<" : "<<val[i]<<endl;
    //        }
            val[rt]=0.00;
            for(reg i=2;i<=n;++i){
                ret+=val[i];
                if(i!=rt&&!pre[i]) return -23333;
                if(!vis[i]) {
                    //cout<<" go "<<i<<endl;
                    top=0;dfs(i);
                    while(top) in[sta[top--]]=0;
                }
            }
            //cout<<" after dfs "<<cnt<<endl;
            for(reg i=2;i<=n;++i) if(!id[i]) id[i]=++cnt;
        //    for(reg i=1;i<=n;++i) cout<<i<<" -- "<<id[i]<<endl;
            if(cnt==n) break;
            for(reg i=1;i<=m;++i){
                if(id[e[i].x]!=id[e[i].y]){
                    //cout<<" cut "<<e[i].x<<" "<<e[i].y<<" "<<e[i].v<<endl;
                    e[i].v-=val[e[i].y];
                }
                e[i].x=id[e[i].x];
                e[i].y=id[e[i].y];
            }
            //cout<<" endneenndd "<<ret<<" cnt "<<cnt<<endl;
            n=cnt;
        }
        return ret;
    }
    void clear(){
        ans=0;
    }
    int main(){
        while(scanf("%d",&n)!=EOF){
            scanf("%d",&m);
            int lp=0;
            clear();
            for(reg i=1;i<=n;++i){
                scanf("%lf%lf",&px[i],&py[i]);
            }
            int x,y;
            for(reg i=1;i<=m;++i){
                scanf("%d%d",&x,&y);
                if(x==y) continue;
                if(y==1) continue;
                e[++lp].x=x;e[lp].y=y;e[lp].v=dist(x,y);
            //    cout<<" x y z "<<x<<" "<<y<<" "<<e[lp].v<<endl;
            }
            m=lp;
            ans=wrk(n);
            if(ans==-23333){
                puts("poor snoopy");
            }else{
                printf("%.2f
    ",ans);
            }
        }
        return 0;
    }
    
    }
    signed main(){
        Miracle::main();
        return 0;
    }
    
    /*
       Author: *Miracle*
       Date: 2019/1/7 17:25:32
    */
    View Code

     

    正常一点的模板:

    luoguP4716 【模板】最小树形图

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #define reg register int
    #define il inline
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    il void rd(int &x){
        char ch;x=0;bool fl=false;
        while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
        (fl==true)&&(x=-x);
    }
    namespace Miracle{
    const int N=105;
    const int M=1e4+5;
    struct node{
        int x,y;
        int v;
    }e[M];
    int id[N],vis[N],pre[N];
    bool in[N];
    int val[N];
    int n,m;
    int rt;
    int sta[N],top;
    int cnt;
    void dfs(int x){
    //    cout<<" x "<<x<<endl;
        sta[++top]=x;in[x]=1;
        vis[x]=1;
        if(pre[x]==rt) return;
        else if(!vis[pre[x]]) dfs(pre[x]);
        else if(in[pre[x]]){
        //    cout<<" new huan "<<endl;
            int z;
            ++cnt;
            do{
                z=sta[top];
                id[z]=cnt;
                in[z]=0;
                top--;
            }while(z!=pre[x]);
        }
    }
    double wrk(int n){
        int ret=0;
        int turn=0;
        while(1){
            ++turn;
        //    cout<<" turn "<<turn<<" "<<n<<endl;
            memset(vis,0,sizeof vis);
            memset(id,0,sizeof id);
            memset(in,0,sizeof in);
            memset(pre,0,sizeof pre);
            memset(val,0x3f,sizeof val);
            top=0;cnt=1;
            id[rt]=cnt;
            for(reg i=1;i<=m;++i){
                if(e[i].x!=e[i].y&&e[i].y!=rt){
                    if(val[e[i].y]>e[i].v){
                        val[e[i].y]=e[i].v;
                        pre[e[i].y]=e[i].x;
                    }
                }
            }
    //        for(reg i=1;i<=n;++i){
    //            cout<<i<<" : "<<pre[i]<<" : "<<val[i]<<endl;
    //        }
            val[rt]=0;
            for(reg i=1;i<=n;++i){
                ret+=val[i];
                if(i==rt) continue;
                if(i!=rt&&!pre[i]) return -1;
                if(!vis[i]) {
                    //cout<<" go "<<i<<endl;
                    top=0;dfs(i);
                    while(top) in[sta[top--]]=0;
                }
            }
            //cout<<" after dfs "<<cnt<<endl;
            for(reg i=1;i<=n;++i) if(!id[i]) id[i]=++cnt;
        //    for(reg i=1;i<=n;++i) cout<<i<<" -- "<<id[i]<<endl;
            if(cnt==n) break;
            for(reg i=1;i<=m;++i){
                if(id[e[i].x]!=id[e[i].y]){
                    //cout<<" cut "<<e[i].x<<" "<<e[i].y<<" "<<e[i].v<<endl;
                    e[i].v-=val[e[i].y];
                }
                e[i].x=id[e[i].x];
                e[i].y=id[e[i].y];
            }
            //cout<<" endneenndd "<<ret<<" cnt "<<cnt<<endl;
            n=cnt;
            rt=id[rt];
        }
        return ret;
    }
    int main(){
        scanf("%d%d%d",&n,&m,&rt);
        int lp=0;
        int x,y,z;
        for(reg i=1;i<=m;++i){
            scanf("%d%d%d",&x,&y,&z);
            e[++lp].x=x;e[lp].y=y;e[lp].v=z;
        }
        m=lp;
        int ans=wrk(n);
        printf("%d",ans);
        return 0;
    }
    
    }
    signed main(){
        Miracle::main();
        return 0;
    }
    
    /*
       Author: *Miracle*
       Date: 2019/1/7 17:25:32
    */
    View Code

     

    (一个看似弱智的问题:为什么prim不能用?因为无向图可以用的原因是,两边都可以走。有向图会堵住一边。使得第一次出队不一定最优

    尝试这个图:

     

     

    扩展:如果根不定呢?

    超级源点,向每个点连接sum(w)+1的边

    由于很大,所以最多连一条这样的边

    如果最后权值>=sum(w)+sum(w)+1,那么实际上是无解的。


    upda:2019.5.6

    最小树形图:O(n+mlogn)算法

    入边整体-Wmin
    从0边不断往上reduce

    到根?直接缩链为点
    有环?ρ形,边权都是0
    先都要上
    缩环
    所有入边可并堆合并起来,再整体减去Wmin(整体标记)
    所有出边用并查集,需要的时候直接定位到这个缩完的点上

    每次-Wmin,就是减去入边的最小权值,因为必然会选择恰好一次

    最后所有减过的Wmin之和就是ans(因为实际上连接的都是0权边,没有额外贡献)

  • 相关阅读:
    JID 2.0 RC4 发布,高性能的 Java 序列化库
    FBReaderJ 1.6.3 发布,Android 电子书阅读器
    Arquillian 1.0.3.Final 发布,单元测试框架
    JavaScript 的宏扩展 Sweet.js
    Hypertable 0.9.6.5 发布,分布式数据库
    JRuby 1.7.0 发布,默认使用 Ruby 1.9 模式
    httppp 1.4.0 发布,HTTP响应时间监控
    Redis 2.6.0 正式版发布,高性能K/V服务器
    OfficeFloor 2.5.0 发布,IoC 框架
    XWiki 4.3 首个里程碑发布
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10236040.html
Copyright © 2011-2022 走看看