处理这样一类问题:
给一个有向图,定义树形图:一个有向图以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 */
或者可以用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 */
正常一点的模板:
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 */
扩展:如果根不定呢?
超级源点,向每个点连接sum(w)+1的边
由于很大,所以最多连一条这样的边
如果最后权值>=sum(w)+sum(w)+1,那么实际上是无解的。