zoukankan      html  css  js  c++  java
  • 生成树

    最小生成树:在无向图中,连接所有的点,不形成环,使所有的边权值和最小的树  

    算法有:prim算法: dijkstra算法类似(dis[i]的含义不同,dijkstra中是起点,prim中是已经访问过的所有点) 稠密图时使用 O(V^2)
    kruskal算法:并查集思想,每次都找到最小的边,判断这两个边连接的点是不是在同一个集合中,如果不是就连接起来 稀疏图时使用 O(ElogE)

    struct node{
    	int v,dis;
    	node(int _v,int _dis) : v(_v),dis(_dis){}
    };
    vector<node> adj[maxn];
    int dis[maxn],vis[maxn];
    int n,m,st,ed;
    void prim(int st){
    	//树的总权值,以及当前所有的连接好了的边
    
    	 for(int i=0;i<n;i++){
    	 	int u=-1,mini=INF; //与dijkstra是不是超级像!!!!!
    		 for(int j=0;j<n;j++){
    		 	if(mini>dis[j]&&vis[j]==0) {
    		 		mini=dis[j];
    				 u=j;
    			 }
    		 } 
    		 if(u==-1) return;
    		 vis[u]=1;
    		 ans+=dis[u]; //!!!!!啊啊啊记住这个 
    		 for(int j=0;j<adj[u].size();j++){
    		 	int id=adj[u][j].v;
    		 	int diss=adj[u][j].dis;
    		 	if(!vis[id]&&dis[id]>diss){
    		 		dis[id]=diss;//!!!! 
    			 }
    		 }
    	 }
    	 
    }
    

    kruskal

    struct edge{
    	int from,to;
    	int dis;
    }E[maxn]; 
    
    int fa[maxn];
    int findfather(int x){
    	if(x!=fa[x]) return findfather(fa[x]);
    	return fa[x];
    }
    bool cmp(edge a,edge b){
    	return a.dis<b.dis;
    }
    void kruskal(int n,int m){
    	//n是顶点数,m是边数
    	for(int i=0;i<n;i++) fa[i]=i;  //先弄这个
    	fill(dis,dis+maxn,INF);
    	memset(vis,0,sizeof(vis));
    	dis[0]=0;
    	int ans=0,numedge=0; //这里才有!!总的权值和现在有了的边
    	 sort(E,E+m,cmp); //对边进行排序
    	 for(int i=0;i<m;i++){
    	 	int fa1=findfather(E[i].from);
    	 	int fa2=findfather(E[i].to);
    	 	if(fa1!=fa2){
    	 		fa[fa2]=fa1;
    	 		numedge++;
    	 		ans+=E[i].dis;
    	 		if(numedge==n-1) break; //!!!!!!!如果边数已经够了的话就可以break了 
    		 }
    	 }
    	 if(numedge!=n-1) {
    	 	cout<<"error no liantong"<<endl;
    	 	return;
    	 } 
    	  else{
    	  	cout<<ans<<endl;
    	  	return;
    	  }
    } 
    

    次小生成树

    这个看懂了https://www.cnblogs.com/bianjunting/p/10829212.html

    下面介绍一下利用prim求次小生成树的主要步骤。
    1.先求出来最小生成树。并将最小生成树任意两点之间路径当中的权值最大的那一条找出来,为什么要找最大的呢,因为生成树加入一条边之后一定构成了回路,
    那么肯定要去掉这个回路当中一条边才是生成树,那么,怎么去边才是次小的,那就去掉除了刚刚添加的一条边之外回路当中权值最大的一个,所以留下的就是最小的。
    2.枚举最小生成树外的每一条边。找出最小的就是次小生成树。

    下面是求解的非严格次小生成树!!!!!!!!!!!!严格的话还需要记录边之间的次大值

    用prim算法求出次小生成树模板

    我们知道Prim算法是以给定的任意点作为起始点运用一定的方法对所有点进行贪心处理,缩点从而生成一颗最小生成树,那我们只需要用数组用来描述最小生成树中每条边的访问情况以及最小生成树中每两个顶点之间的最大边权还需要保存最小生成树中每个顶点的父亲顶点,从而就可以方便用于计算次小生成树。

    具体操作:

    初始化:初始化所有点( i )距离最小生成树子树的距离为cost[source][ i ],所有边初始化为未访问,所有顶点之间的最大边权初始化为0。

    加边:每次加入一条安全边(这里不对安全边进行解释,有不了解的可以查阅博主的上一篇博客),并将最小生成子树中顶点之间的最大边权进行更新,接着更新lowc即可。

    求解次小生成树:我们逐一枚举出所有不属于最小生成树的边(u, v),并且用w(u, v)来替代最大边权和Max(u, v),怎么个替代法? 

      SecondMST = min(MST + w(u, v) - Max(u, v))    ((u, v) not belong to MST)。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int maxn = 1000 + 10, INF = 0x3f3f3f3f;
    int n, m, lowc[maxn], pre[maxn], Max[maxn][maxn];  //记录最大边
    int cost[maxn][maxn]; bool vis[maxn];
    bool used[maxn][maxn]; //记录有没有在最小生成树中被使用过 int Prim() { int ans = 0; memset(vis, false, sizeof vis); memset(Max, 0, sizeof Max); memset(used, false, sizeof used); vis[1] = true; pre[1] = -1; //需要记录前驱 for(int i = 2; i <= n; i ++) { lowc[i] = cost[1][i]; //先把能到达的赋值了 pre[i] = 1; } lowc[1] = 0; for(int i = 2; i <= n; i ++) { int MIN = INF, p = -1; for(int j = 1; j <= n; j ++) { if(!vis[j] && MIN > lowc[j]) { MIN = lowc[j]; p = j; } } if(MIN == INF) return -1; ans += MIN; vis[p] = true; used[p][pre[p]] = used[pre[p]][p] = true; //记录使用 for(int j = 1; j <= n; j ++) { if(vis[j] && j != p) Max[j][p] = Max[p][j] = max(Max[j][pre[p]], lowc[p]); //最大值的两种情况 if(!vis[j] && lowc[j] > cost[p][j]) { lowc[j] = cost[p][j]; pre[j] = p; } } } return ans; } int Second_Prim(int MST) { int ans = INF; for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++) if(!used[i][j] && cost[i][j] != INF) ans = min(ans, MST - Max[i][j] + cost[i][j]);
    //在所有的情况里面取最小值,减去max,加上原本的长度 return ans; } int main() { int t, a, b, c; scanf("%d", &t); while(t --) { scanf("%d %d", &n, &m); for(int i = 0; i <= n; i ++) for(int j = 0; j <= n; j ++) cost[i][j] = INF; for(int i = 1; i <= m; i ++) { scanf("%d %d %d", &a, &b, &c); cost[a][b] = cost[b][a] = c; } int MST = Prim(); int Second_MST = Second_Prim(MST); printf("%d ", Second_MST); } return 0; }

    kruskal算法求出次小生成树模板

    Kruskal算法是将图G的所有边进行排序,从小到大满足边的两个顶点有一个不在subMST中就将其加入MST,在求解次小生成树问题时我们也需要记录MST中结点的连接情况,以及MST中两个顶点间的最大边权。

    具体操作:

    初始化:初始化并查集,初始化在subMST中每个结点 i 直接或者间接相连的边为i。

    加边:每次加入一条边时,我们更新subMST中所有与u, v相连的结点间的最大边权,接着将所有与结点v相连的边都与结点u也连起来就行了(前提是在合并时head[ head[ v ] ] = head[ u ])。

    求解次小生成树:

     SecondMST = min(MST + w(u, v) - Max(u, v))    ((u, v) not belong to MST)

    //kruskal算法 
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    #define N 1001
    #define M 100001
    const int INF=0x3fffffff;
    typedef long long LL;
    using namespace std;
    int n,m,pre[N],fa[N],maxx[N][N]; //还是要记录最长和前驱
    //并查集思想 struct edge{ int u,v,w; bool vis; //相当于used,标记这条边有没有被访问过 }ed[M]; vector<int> g[N]; bool cmp(edge a,edge b){ return a.w<b.w; } void inti(){ for(int i=1;i<=n;i++){ g[i].clear(); g[i].push_back(i); fa[i]=i; } } int findf(int x){ if(x==fa[x]) return x; else return fa[x]=findf(fa[x]); } int kruskal(){ sort(ed+1,ed+1+m,cmp); inti(); int ans=0,cnt=0; for(int i=1;i<=m;i++){ if(cnt==n-1) break; //已经ok了 int fa1=findf(ed[i].u); int fa2=findf(ed[i].v); if(fa1!=fa2){ cnt++; ed[i].vis=1; ans+=ed[i].w; int len_fx=g[fa1].size(); //两个的长度 int len_fy=g[fa2].size(); for(int j=0;j<len_fx;j++){ for(int k=0;k<len_fy;k++){ //两次循环 maxx[g[fa1][j]][g[fa2][k]]=maxx[g[fa2][k]][g[fa1][j]]=ed[i].w; //这就是最大 } } fa[fa1]=fa2; //让fa2当老大 for(int j=0;j<len_fx;j++){ g[fa2].push_back(g[fa1][j]); //都送到fa2家里去 } } } return ans; } int second_kruskal(int mst){ int ans=INF; for(int i=1;i<=m;i++){ if(!ed[i].vis){ //这条边没有访问过 ans=min(ans,mst+ed[i].w-maxx[ed[i].u][ed[i].v]); } } return ans; } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d %d %d",&ed[i].u,&ed[i].v,&ed[i].w); ed[i].vis=0; } int mst=kruskal(); int sec=second_kruskal(mst); printf("%d ",sec); } return 0; }

    POJ1679 The Unique MST

    求次小生成树唯一与否,用prim或者kruskal都可以

    用prim算法的话:只要最小生成树和次小生成树权值和一样就唯一。因此得出如下算法,首先计算出最小生成树T,然后对最小生成树上任意不相邻的两个点 uv添加最小生成树以外的存在的边形成环,然后寻找u与v之间最小生成树上最长的边删去,计算map[i][j]与 maxd[i][j差值,求出最小的来,如果是0,就说明MST和次小生成树一样

    int mp[maxn][maxn];
    int maxx[maxn][maxn];  ////表示最小生成树中i到j的最大边权
    bool used[maxn][maxn]; //判断该边是否加入最小生成树
    int pre[maxn];
    bool vis[maxn];
    int dis[maxn];
    void inti(int n){
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			if(i==j) mp[i][j]=0;
    			else mp[i][j]=INF;
    		}
    	}
    }
    void rea(int m){
    	int u,v,w;
    	for(int i=0;i<m;i++){
    		scanf("%d %d %d",&u,&v,&w);
    		mp[u][v]=mp[v][u]=w;
    	}
    }
    int prim(int n){
    	int ans=0;
    	memset(vis,0,sizeof(vis));
    	memset(used,0,sizeof(used));
    	memset(maxx,0,sizeof(maxx));
    	for(int i=2;i<=n;i++){
    		dis[i]=mp[1][i];
    		pre[i]=1; //初始化前驱,距离,vis
    	}
    	pre[1]=0;
    	dis[1]=0;
    	vis[1]=true;
    	for(int i=2;i<=n;i++){
    		int min_dis=INF;
    		int k;
    		for(int j=1;j<=n;j++){
    			if(!vis[j]&&min_dis>dis[j]){
    				min_dis=dis[j];
    				k=j;
    			}
    		}
    		if(min_dis==INF) return -1; //不存在最小生成树
    		ans+=dis[k];
    		 vis[k]=1;
    		used[k][pre[k]]=used[pre[k]][k]=true; //在最小生成树中被使用
    		for(int j=1;j<=n;j++){
    			if(vis[j]){
    				maxx[j][k]=maxx[k][j]=max(maxx[j][pre[k]],dis[k]);
    			}
    			if(!vis[j]&&dis[j]>mp[k][j]) {
    				dis[j]=mp[k][j];
    				pre[j]=k;
    			}
    		} 
    	}
    	return ans; //最小生成树的权值之和
    }
    int smet(int n,int min_dis)//min_ans 是最小生成树的权值和
    {
    	int ans=INF;
    	for(int i=1;i<=n;i++){
    		for(int j=i+1;j<=n;j++){  //枚举最小生成树之外的边
    			if(mp[i][j]!=INF&&!used[i][j]){
    				ans=min(ans,min_dis+mp[i][j]-maxx[i][j]); 
    			}
    		}
    	}
    	if(ans==INF) return -1;
    	return ans;
    }
    void solve(int n){
    	int ans=prim(n);
    	if(ans==-1) {
    		printf("Not Unique!
    ");
    		return;
    	}
    	if(smet(n,ans)==ans) printf("Not Unique!
    ");  //最小和次小相同
    	else printf("%d
    ",ans);
    }
    int main(){
    	int t,n,m;
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d %d",&n,&m);
    		inti(n);
    		rea(m);
    		solve(n);
    	}
    return 0;
    }
    

    如果是kruskal算法的话,次小生成树唯不唯一就是看权重相同的另一条边,如果两边的爸爸妈妈还是当前的爸爸妈妈,那就不唯一 

    但是这个代码有错TAT不知道为什么

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    #define N 1001
    #define M 100001
    const int INF=0x3fffffff;
    typedef long long LL;
    using namespace std;
    int n,m,fa[N];
    int size[N]; //加这个size是因为可以在合并的时候快一点
    struct edge{
    	int head,to,val;
    }ed[M]; 
    int cnt;
    bool cmp(edge a,edge b){
    	return a.val<b.val;
    }
    void inti(){
    	for(int i=0;i<=n+1;i++){
    		size[i]=0;
    		fa[i]=i;
    	}
    }
    void insr(int a,int b,int c){
    	ed[cnt].head=a;
    	ed[cnt].to=b;
    	ed[cnt++].val=c;
    }
    int findf(int x){
    	if(x==fa[x]) return x;
    	else return fa[x]=findf(fa[x]);
    }
    void uni(int a,int b){
    	int fa1=findf(a);
    	int fa2=findf(b);
    	if(fa1!=fa2){
    		if(size[fa1]<=size[fa2]){
    			size[fa2]++;
    			fa[fa1]=fa2;
    		}
    		else{
    			size[fa1]++;
    			fa[fa2]=fa1;
    		}
    	}
    }
    int kruskal(){
    	sort(ed,ed+cnt,cmp);
    	inti();
    	bool f=0;
    	int ans=0;
    	for(int i=0;i<cnt;i++){
    		if(findf(ed[i].head)==findf(ed[i].to)) continue;
    		int fa1=findf(ed[i].head);
    		int fa2=findf(ed[i].to);
    		//看次小生成树唯不唯一就是看权重相同的另一条边,如果两边的爸爸妈妈还是当前的爸爸妈妈,那就不唯一 
    		for(int j=i+1;j<cnt;j++){  //在后面的边里面找
    			if(ed[j].val!=ed[i].val) continue;  //权值要相等哦,因为唯一嘛
    			if(fa1==findf(ed[j].head)&&fa2==findf(ed[j].to)){
    				f=1;
    				break;
    			} 
    		}
    		uni(ed[i].head,ed[i].to); //合并
    		ans+=ed[i].val;
    		if(f) break;
    	}
    	if(f) return -1;
    	else return ans;
    }
    
    int main(){
    	int t;
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d %d",&n,&m);
    		inti();
    		for(int i=1;i<=m;i++){
    			int u,v,w;
    			scanf("%d %d %d",&u,&v,&w);
    			if(u>v) insr(v,u,w);
    			else insr(u,v,w);
    		}
    		int ans=kruskal();
    		if(ans<0) printf("Not Unique!
    ");
    	else 	printf("%d
    ",ans);
    	}
    	
    	return 0;
    }
     
    

    最优比率生成树

    这个博客介绍的很好:

    https://www.cnblogs.com/lotus3x/archive/2009/03/21/1418480.html

    这个是分数规划模型,可以用二分,也可以用Dinkelbach进行迭代(更快)

    POJ 2728 Desert King

    http://poj.org/problem?id=2728

    二分:

    优比率生成树题意与最小生成树基本相同,但由单一边权的最小值转化为第一边权的总和与第二边权的总和比值的最小值,这导致算法发生巨大变化,以致于需要采用二分的方法,并进行一系列复杂的判定……
    对于给定的有向图,要求求出一颗子树G,使其各边收益总和与花费的总和比值尽可能小,即Σ(benifit[i])/Σ(cost[i]) i∈G,我们可以二分答案λ的上下界[0,∞)(事实上上界取2^就好了),当λ为最优解时f(λ)=Σ(benifit[i])-λ*Σ(cost[i])=Σ(d[i])=0 i∈G(d[i]=benifit[i]-λ*cost[i])
    接着我们很容易得知,f(x)是单调递减的,因此,若f(λ)≠0,便可缩小搜索范围,二分复杂度是log(max)。至于求f(λ)的值,明显要使其尽可能小,又要满足构成树,以d[i]为边权的最小生成树便可以满足要求。
    我们理一下代码思路:
    ①按上下界二分λ,开始,判定;
    ②更新单一边权d[i]=benifit[i]-λ*cost[i];
    ③求出此时的最小生成树;
    ④若f(x)=0,则λ为所求解,否则继续循环;

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1210;
    const int INF=0x3fffffff;
    typedef long long LL;
    
    //二分 
    double dis[maxn];
    double gra[maxn][maxn];
    int n;
    int vis[maxn];
    void inti(){
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			gra[i][j]=INF;
    			if(i==j) gra[i][j]=0;
    		}
    	}
    } 
    struct node{
    	double x,y,z;
    }p[maxn];
    double value[maxn][maxn];
    double cost[maxn][maxn];
    double js(node a,node b){
    	return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2)); //距离
    }
    bool input(){
    	scanf("%d",&n);
    	if(n==0) return 0;
    	for(int i=1;i<=n;i++){
    		scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].z); //坐标+高度
    	}
    	return 1;
    }
    void build(double mid){
    	inti();
    	for(int i=1;i<=n;i++){
    		for(int j=i+1;j<=n;j++){
    			gra[i][j]=gra[j][i]=-mid*js(p[i],p[j])+fabs(p[i].z-p[j].z); //d[i] = benifit[i] - l * cost[i])
    //更新边权
    } } } double prim(){ double ans=0; for(int i=1;i<=n;i++){ vis[i]=0; dis[i]=gra[1][i]; //能到的先赋值 } vis[1]=1; for(int num=1;num<n;num++){ int temp=INF,index; for(int i=1;i<=n;i++){ if(!vis[i]&&dis[i]<temp){ temp=dis[i]; index=i; } } vis[index]=1; for(int i=1;i<=n;i++){ if(!vis[i]&&dis[i]>gra[index][i]) dis[i]=gra[index][i]; } } for(int i=1;i<=n;i++) ans+=dis[i]; //计算总长度,最优值的最长度是0 return ans; } void solve(){ double l,r,mid; l=0;r=100; while(r-l>0.0001){ mid=(l+r)/2; build(mid); if(prim()>0) l=mid; //增大,因为最优值是等于0 else r=mid; } printf("%.3lf ",l); } int main(){ while(input()){ solve(); } return 0; }

    迭代

    //迭代
    #define maxn 1010
    #define eps 1e-6
    const int INF=0x3fffffff;
    typedef long long LL;
    using namespace std;
    double d[maxn][maxn];  //这个是两者的距离,也就是长度,benifit 
    double hei[maxn][maxn];  //高度,也就是花费 
    double g[maxn][maxn],dis[maxn];
    double dt,ht,zl;
    int n,vis[maxn],pre[maxn];
    void prim(double L){
    	int i,j,mid;
    	double minn;
    	dt=0;  //总长度 
    	ht=0;  //总花费 
    	zl=0;  //当前比值生成的最小生成树权值和 
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			g[i][j]=hei[i][j]-L*d[i][j]; //花费-长度*mid
    		}
    	}
    	for(int i=1;i<=n;i++){
    		dis[i]=INF;
    		vis[i]=0;
    	}
    	pre[1]=1;
    	dis[1]=0.0;
    	for(int i=0;i<n;i++){
    		int temp=INF,u;
    		for(int j=1;j<=n;j++){
    			if(!vis[j]&&dis[j]<temp){
    				temp=dis[j];
    				u=j;
    			}
    		}
    		vis[u]=1;
    		dt+=d[u][pre[u]];  //总长度 
    		ht+=hei[u][pre[u]];  //总花费 
    		zl+=dis[u];  //总长度,应该为0才对
    		for(int j=1;j<=n;j++){
    			if(!vis[j]&&dis[j]>g[u][j]){
    				dis[j]=g[u][j];
    				pre[j]=u;
    			}
    		}
    	}
    }
    double x[maxn],y[maxn],z[maxn];
    
    int main(){
    	while(scanf("%d",&n),n!=0){
    		double maxx=0,L=0;
    		for(int i=1;i<=n;i++){
    			scanf("%lf %lf %lf",&x[i],&y[i],&z[i]);
    		}
    		 maxx=0;
    		for(int i=1;i<=n;i++){
    			for(int j=1;j<=n;j++){
    				d[i][j]=sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2));  //距离
    				hei[i][j]=fabs(z[i]-z[j]);  //高度
    				maxx=max(maxx,hei[i][j]);
    			}
    	}
    	L=n*maxx;   //边界条件
    	while(prim(L),fabs(zl)>eps) L=ht/dt; 
    	printf("%.3lf
    ",L);
    	}
    	
    	return 0;
    } 
    

    最小度限制生成树

    某个特殊节点的度等于给定值,最小度限制生成树就是满足这种性质并且权值和最小的树,如果T是G的一个生成树且dT(v0)=k,则称T为G的k度限制生成树。

    可行交换

    HOW:(1)先求出k的最小值(下界),把v0从图中删掉后,会出现m个连通分量,所以d(v0)>=m,也就是说当k<m时无解

    (2)有最小m度限制生成树得到最小m+1度限制生成树

    (3)当dT(v0)=k时停止

    最小p+1度生成树是由最小p度限制生成树经过一次可行交换(+a1,-b1)得到的,任意的可行交换,一定是一条与v0相关,一条与v0无关的,进一步优化,加一条与v0有关的边,去掉一条在环上与v0无关的且权值最大的边(dp)

    设best(v)为路径v0--v上与v0无关联且权值最大的边,定义fa(v)为v的父节点,转移方程为best(v)=max(best(fa(v),w(fa[v],v)),边界条件为best[v0]=-INF,best[v']=-INF((v0,v')属于E(T))

    总复杂度为O(Elog2V+kV)

    【例题】POJ 1639

    目的地的度有限制,求所有路线和权值的最小值 

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=25;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //目的地的度有限制,求所有路线和权值的最小值 
    map<string,int> mymap;
    map<int,int> myi;
    struct node{
        int u,v,c;
        bool use;
    };
    node *p;
    int n,m,e,k;
    int res,ans,cnt,sm; 
    int fa[maxn],best[maxn],head[maxn],nn[maxn];
    int *nex;
    void adde(int u,int v,int c){
        p[e].u=u;p[e].v=v;
        p[e].c=c;
        p[e].use=false;
        e++;
    }
    void get_next(){
        memset(head,-1,sizeof(head));
        memset(nn,0,sizeof(nn));
        myi.clear();
        for(int i=e-1;i>=0;i--){
            nex[i]=head[p[i].u];
            head[p[i].u]=i;
            nn[p[i].u]++;  //
            myi[p[i].u*maxn+p[i].v]=i; //这个是为什么呢 
        }
    }
    int cmp(const void *a,const void *b){
        node *aa=(node *)a;
        node *bb=(node *)b;
        return aa->c-bb->c;  //这里改为aa->c>=bb->c就会错???为啥  
    }
    void inti(int n){
        for(int i=0;i<=n;i++) fa[i]=i;
    }
    int findfa(int x){
        if(x==fa[x]) return x;
        else return fa[x]=findfa(fa[x]);
    }
    void dfs(int x){
        for(int i=head[x];i+1;i=nex[i]){
            if(p[i].use&&p[i].v){   //这条边使用过并且连接了另一个不为0的顶点 
                 fa[p[i].v]=x;
                 p[i].use=p[myi[p[i].v*maxn+x]].use=false;  //需要加上一条与v0有关的边,去掉一条与v0无关的并且最长的边 
                 dfs(p[i].v);
            }
        }
    }
    void kruskal(){
        res=INF;
        ans=0;  //临时存最小值的 
        cnt=0;
        get_next();
        for(int i=0;i<e;i++){
            if(p[i].u==0||p[i].v==0) continue; //一开始不连接v0,计算剩下的连通分量
            int x=findfa(p[i].u);
            int y=findfa(p[i].v);
            if(x==y) continue;  
            p[i].use=p[myi[p[i].v*maxn+p[i].u]].use=true;//标记使用 
            cnt++; 
            ans+=p[i].c;
            fa[x]=y;
        }
        m=n-1-cnt;  //这个剩下的连通块数,k必须大于m才行
        if(k<m) return;
        //把剩下的与v0相关联的边连接起来,形成完整的最小生成树(边数为n-1) 
        for(int i=head[0];i+1;i=nex[i]){
            if(p[i].u&&p[i].v){
                continue;
            }
            int x=findfa(p[i].u);
            int y=findfa(p[i].v);
            if(x==y) continue;
            p[i].use=true;   //标记使用 
            fa[x]=fa[y]=0;
            cnt++;
            ans+=p[i].c;
            if(cnt==n-1) break;
        } 
        dfs(0);
        res=ans;
    }
    int get_best(int x){  //动态规划求最大的与v0无关的最长边 
        if(fa[x]==0) return -1;  //与v0有关的边赋值都为-1(-INF) 
        if(best[x]!=-1) return best[x]; 
        int temp=get_best(fa[x]);
        if(p[myi[fa[x]*maxn+x]].c>temp) best[x]=p[myi[fa[x]*maxn+x]].c;
        else best[x]=temp;
        return best[x];
    }
    void cut_edge(){
        while(m<k&&m<nn[0]){   //注意这个限制 
            m++;
            memset(best,-1,sizeof(best));
            for(int i=0;i<n;i++) get_best(i);
            int a=INF;
            int y=0;
            for(int i=head[0];i+1;i=nex[i]){
                if(p[i].use) continue;  //因为需要加上一条与v0有关的边 
                if(best[p[i].v]!=-1&&a>p[i].c-best[p[i].v]){  //前面求了best数组,所以在这里直接减去就可以了,并且求最小值 
                    a=p[i].c-best[p[i].v];
                    y=p[i].v;
                }
            }
            ans+=a;  //加上一条边 
            fa[y]=0;
            res=min(ans,res);
        }
    }
    int main(){
        while(scanf("%d",&m)!=EOF){
            p=new node[2*m+5];
            nex=new int[2*m+5];
            mymap.clear();
            n=e=0;
            string s1,s2;
            mymap["Park"]=n++;
            for(int i=0;i<m;i++){
                int c;
                cin>>s1>>s2>>c;
                if(mymap.find(s1)==mymap.end()) mymap[s1]=n++;
                if(mymap.find(s2)==mymap.end()) mymap[s2]=n++;
                adde(mymap[s1],mymap[s2],c);
                adde(mymap[s2],mymap[s1],c);
            }
            scanf("%d",&k);  //
            qsort(p,e,sizeof(p[0]),cmp);
            inti(n);
            kruskal();
            if(res==INF) continue;
            cut_edge();
            printf("Total miles driven: %d
    ",res);
            delete [] p;
            delete [] nex; 
        }
    return 0;
    } 
    View Code
  • 相关阅读:
    #define、const、typedef的区别
    《软件调试的艺术》学习笔记——GDB使用技巧摘要
    作为员工与老板的竞争力区别
    部分经典IT书籍
    对找工作功不可没——评《深入理解计算机系统》
    前端UI框架小汇总
    Visual Studio 2017 序列号 Key 激活码 VS2017 注册码
    Wampserver 2.5 多站点配置方法
    ThinkPHP3.1快速入门(9)变量输出
    ThinkPHP3.1快速入门(8)视图
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12489263.html
Copyright © 2011-2022 走看看