[學習筆記]最小樹形圖——朱劉演算法
阿新 • • 發佈:2019-01-07
處理這樣一類問題:
給一個有向圖,定義樹形圖:一個有向圖以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> #defineView Codereg 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 */
或者可以用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,那麼實際上是無解的。