1. 程式人生 > >[學習筆記]最小樹形圖——朱劉演算法

[學習筆記]最小樹形圖——朱劉演算法

處理這樣一類問題:

給一個有向圖,定義樹形圖:一個有向圖以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\n"); }else{ printf("%.2f\n",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\n",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

 

 

擴充套件:如果根不定呢?

超級源點,向每個點連線sum(w)+1的邊

由於很大,所以最多連一條這樣的邊

如果最後權值>=sum(w)+sum(w)+1,那麼實際上是無解的。