首页 > 试题广场 >

继续畅通工程

[编程题]继续畅通工程
  • 热度指数:11320 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 64M,其他语言128M
  • 算法知识视频讲解
    省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全省畅通需要的最低成本。

输入描述:
    测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( 1< N < 100 );随后的 N(N-1)/2 行对应村庄间道路的成本及修建状态,每行给4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态:1表示已建,0表示未建。

    当N为0时输入结束。


输出描述:
    每个测试用例的输出占一行,输出全省畅通需要的最低成本。
示例1

输入

3
1 2 1 0
1 3 2 0
2 3 4 0
3
1 2 1 0
1 3 2 0
2 3 4 1
3
1 2 1 0
1 3 2 1
2 3 4 1
0

输出

3
1
0
/*常规的并查集方法,构建最小生成树,用sort对代价大小进行排序。这里只要稍微修改,小于号的重载方式,即优先让建好的排在前面,就能够优先参与并查操作,需要用到未建通路时再加上算总代价*/




#include <stdio.h>
#include <algorithm>
using namespace std;
#define N 100
int tree[N];
int findroot(int x){
if(tree[x]==-1)return x;
else{
int tmp=findroot(tree[x]);
tree[x]=tmp;
return tmp;
}
}
struct edge{
int a,b,status;
int cost;
bool operator <(const edge &a)const{ 
//重载 将状态为1的放在前面 优先并查 其余按代价升序排列
if(status==a.status)
return cost<a.cost;
else return status>a.status;
}
}edge[5000];
int main(){
int n;
while(scanf("%d",&n)!=EOF&&n!=0){
for(int i=1;i<=n*(n-1)/2;++i)
scanf("%d%d%d%d",&edge[i].a,&edge[i].b,&edge[i].cost,&edge[i].status);
sort(edge+1,edge+1+n*(n-1)/2);
for(int i=1;i<=n;++i)tree[i]=-1;
int ans=0;
for(int i=1;i<=n*(n-1)/2;++i){
int a=findroot(edge[i].a);
int b=findroot(edge[i].b);
if(a!=b){
tree[a]=b;
if(edge[i].status==0)ans+=edge[i].cost; //只有状态为0才参与计算
}
}
printf("%d\n",ans);
}
return 0;
}
编辑于 2018-01-22 00:15:53 回复(0)

Kruskall求最小生成树的改版,题目已经提前提供了一些边

发表于 2016-10-28 14:57:54 回复(0)
import java.util.Scanner;
import java.util.Arrays;
import java.util.Comparator;

class edge{
    private int u;
    private int v;
    private int w;
    
    public edge(int u, int v, int w) {
        this.u = u;
        this.v = v;
        this.w = w;
    }
    public int getU() {
        return u;
    }
    public int getV() {
        return v;
    }
    public int getW() {
        return w;
    }
    public void setW(int w){
        this.w = w;
    }
}
public class Main {
    static int f[] = new int[100];
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int n = input.nextInt();    //顶点数
        int m = n*(n-1)/2;    //边数
        edge e[] = new edge[m];
        for(int i=0;i<m;i++){
            int u = input.nextInt();
            int v = input.nextInt();
            int w = input.nextInt();
            e[i] = new edge(u,v,w);
            int status = input.nextInt();
            if(status == 1){
                e[i].setW(0);
            }
        }
        //按边的权值从小到大排序
        Arrays.sort(e, new Comparator<edge>(){
            public int compare(edge o1, edge o2) {
                if(o1.getW()>o2.getW()){
                    return 1;
                }else if(o1.getW()<o2.getW()){
                    return -1;
                }else{
                    return 0;
                }
            }
        });
        //并查集初始化,数组里存的是自己数组的下标编号
        for(int i=0;i<n;i++){
            f[i] = i;
        }
        //Kruskal算法核心部分
        int count = 0,sum = 0;
        for(int i=0;i<m;i++){        //从小到大枚举每一条边
            if(merge(e[i].getU(),e[i].getV())){        //判断两个点是否联通,不连通则用这条边
                count++;
                sum += e[i].getW();
            }
            if(count == n-1){
                break;
            }
        }
        System.out.println(sum);
    }
    //并查集合并两个子集合
    public static boolean merge(int u,int v){
        int t1 = getf(u);
        int t2 = getf(v);
        if(t1 != t2){    //判断两个点是否在同一个集合中
            f[t2] = t1;
            return true;
        }
        return false;
    }
    //并查集寻找祖先
    public static int getf(int v){
        if(f[v]==v){
            return v;
        }else{
            f[v] = getf(f[v]);        //路径压缩
            return f[v];
        }
    }
}

发表于 2018-06-12 18:30:25 回复(0)
/**
这题使用了并查集的思想,造了最小生成树,因为事先按花费排序,所以这棵树一旦生成,就是代价最小
**/
#include<iostream>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
int tree[101];
int findroot(int x) {
    if (tree[x] == -1)
        return x;
    else {
        int tmp = findroot(tree[x]);
        tree[x] = tmp;
        return tmp;
    }
}
struct e{
    int l,r,fee;
     bool operator<(const e &a) const {
       return fee < a.fee;
    }
} e[5000];
int main() {
    int n=0;

    int left,right,fee,status;
    while(cin >>n && n) {

        for (int i = 1; i <= n * (n - 1) / 2; ++i){
             cin>>left>>right>>fee>>status;
        if(status == 1) {
            e[i].l = left;
            e[i].r = right;
            e[i].fee = 0;
        } else {
           e[i].l = left;
           e[i].r = right;
           e[i].fee = fee;
        }
        }
        sort(e + 1, e + 1 + n * (n - 1) / 2);
        for (int i = 1; i <= n; ++i)
            tree[i] = -1;

        int ans = 0;
        for (int i = 1; i <= n * (n - 1) / 2; ++i) {
            int a = findroot(e[i].l);
            int b = findroot(e[i].r);
            if (a != b) {
                tree[a] = b;
                ans += e[i].fee;
            }
        }

        cout<<ans<<endl;
    }


}

发表于 2018-06-03 15:04:20 回复(3)
还是畅通工程的改版~
#include <stdio.h>

typedef struct Edge{
    int from,to,len,flag;
}Edge;

Edge e[10000];
int dad[100],h[100];

void Initial(int n)
{
    for(int i=0;i<=n;i++)
    {
        dad[i]=i;
        h[i]=0;
    }    
}

int Find(int x)
{
    if(x!=dad[x])
        dad[x]=Find(dad[x]);
    return dad[x];
}

void Union(int x,int y)
{
    x = Find(x);
    y = Find(y);
    if(x!=y)
    {
        if(h[x]<h[y])    dad[x]=y;
        else if(h[x]>h[y])    dad[y]=x;
        else{
            dad[y]=x;
            h[x]++;
        }
    }
    return;
}

int cmp(const void *a,const void *b)
{
    if((*(Edge*)a).flag==(*(Edge*)b).flag)
        return (*(Edge*)a).len-(*(Edge*)b).len;
    else
        return (*(Edge*)b).flag-(*(Edge*)a).flag;
}

int Kruskal(int n,int num)
{
    Initial(n);
    qsort(e,num,sizeof(e[0]),cmp);
    int sum = 0;
    for(int i=0;i<num;i++)
    {
        if(Find(e[i].from)!=Find(e[i].to))
        {
            Union(e[i].from, e[i].to);
            if(e[i].flag==0)
                sum+=e[i].len;
        }
    }
    return sum;
}

int main()
{
    int n,m,ans,i;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        m=n*(n-1)/2;
        for(i=0;i<m;i++)
        {
            scanf("%d %d %d %d",&e[i].from,&e[i].to,&e[i].len,&e[i].flag);
        }
        ans=Kruskal(n, m);
        printf("%d\n",ans);
    }
    return 0;
}


发表于 2021-02-28 22:42:45 回复(0)
/*
*
*克鲁斯卡尔最小生成树,重点是边的排序,这里是否建立优先,其次是花费(能不花就不花)。
*但是,是否已经建立的情况下,花费的排序就多余了,可以再增加一个距离(花费优先)。
*/

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e4;
struct edge
{
    int u, v, f, w;
    edge(int _u=0,int _v=0,int _w=0,int _f=0):u(_u),v(_v),w(_w),f(_f){}
};
vector<edge> edges;      //边集
int father[maxn+5];      //并查集表征连通分量
int n, m;

bool cmp(edge a, edge b)   //当然是能不花钱就不花钱  但是貌似f==1时,w的大小比较没啥用(可以再增加一个距离,花费优先)
{
    if(a.f == b.f) return a.w < b.w;
    else return a.f > b.f;
}

void Initial()   //初始化并查集
{
    for(int i = 1;i <= n; i++) father[i] = i;
}

int getFather(int x)   //找根
{
    int a = x;
    while(x != father[x]) x = father[x];
    //路径压缩
    while(a != father[a])
    {
        int z = a; a = father[a]; father[z] = x;
    }
    return x;
}

void Create()    //建图
{
    int u, v, w, f;
    for(int i = 0;i < m; i++)
    {
        cin >> u >> v >> w >> f;
        edges.push_back(edge(u, v, w, f));
    }
}

int Kruskal()     //克鲁斯卡尔
{
    Initial();
    sort(edges.begin(), edges.end(), cmp);
    int ans = 0, num = 0;
    for(int i = 0;i < edges.size(); i++)
    {
        int fu = getFather(edges[i].u);
        int fv = getFather(edges[i].v);
        if(fu != fv)     //u  和  v  不连通则连通   如果连通相连就会形成环(×) 
        {
            father[fu] = fv;
            ans += edges[i].f ? 0 : edges[i].w; num++;
            if(num == n-1) break;
        }
    }
    //if(num != n-1) return -1;   //图非连通 
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);    
    while(cin >> n && n)
    {
        m = n*(n-1)/2;
        Create();
        int ans = Kruskal();
        //if(ans == -1) cout << "Error" << '\n';   //图非连通
        cout << ans << '\n';
    }
    return 0;
}

发表于 2021-01-22 20:45:40 回复(0)
#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
struct edge
{
	int start, end, cost, built;
	edge(int s, int e, int c, int b) :start(s), end(e), cost(c), built(b) {};
};

int find_root(map<int, int>& V, int x)//查找x所在集合的根
{
	if (V.find(x) != V.end() && V[x] != x)
		V[x] = find_root(V, V[x]);//递归进行路径压缩
	else
		return x;
	return V[x];
}

int kruskal(vector<edge>& E, int n)
{
	int cost = 0, count = 0;
	map<int, int> V;
	vector<edge>::iterator it = E.begin();
	while (count != n - 1)
	{
		while (it != E.end())
		{			
			int a = find_root(V, it->start);
			int b = find_root(V, it->end);
			//注意要保证不在集合内,因为可能已建成的形成了环路
			if (a != b)
			{
				if (it->built == 1)
				{
					V[b] = a;
					it++;
					break;
				}
				else
				{
					V[b] = a;
					cost += it->cost;
					it++;
					break;
				}
			}
			else
				it++;
		}
		count++;
	}
	return cost;
}
bool comp(edge e1, edge e2)//排序函数
{
	if (e1.built == e2.built)
		return e1.cost < e2.cost;
	else
		return e1.built > e2.built;
}
int main()
{
	int n;
	vector<edge> E;
	while (cin>>n && n != 0)
	{
		int edge_num = n * (n - 1) / 2;
		for (int i = 0; i < edge_num; i++)
		{
			int s, e, c, b;
			cin >> s >> e >> c >> b;
			E.push_back(edge(s, e, c, b));
		}
		sort(E.begin(), E.end(), comp);
		cout << kruskal(E, n) << endl;
		E.clear();
	}
	return 0;
}

kruskal算法,注意已经建成的形成了环路
编辑于 2020-07-01 16:03:20 回复(0)
当然Kruskal更方便,但是为了应付考试和增强自己的能力,这道题我用了Prim算法来完成
#include<iostream>
(720)#include<algorithm>
#include<cstring>
(803)#include<limits.h>
using namespace std;
struct Edge{
    int IsOk;
    int cost;
}e[101][101];
int lowcost[101];
bool visit[101];
int main(){
    int n;
    int x,y,money,statu;
    while(cin>>n){
        if(n==0)    break;
        int spend=0;
        memset(visit,false,sizeof(visit));
        int AllE=n*(n-1)/2;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                e[i][j].cost=INT_MAX;
        for(int i=0;i<AllE;i++){
            cin>>x>>y>>money>>statu;
            e[x][y].cost=money;
            e[x][y].IsOk=statu;
            e[y][x].cost=money;
            e[y][x].IsOk=statu;
        }
        for(int i=2;i<=n;i++){
            if(e[1][i].IsOk)    lowcost[i]=0;
            else    lowcost[i]=e[1][i].cost;
        }
        visit[1]=true;
        for(int i=2;i<=n;i++){
            int min=INT_MAX;
            int v=-1;
            for(int j=1;j<=n;j++){
                if(visit[j]==false){
                    if(min>lowcost[j]){
                        min=lowcost[j];
                        v=j;
                    }
                }
            }
            if(v!=-1){
                spend+=min;
                visit[v]=true;
                for(int j=1;j<=n;j++){
                    if(visit[j]==false){
                        if(e[v][j].IsOk)    lowcost[j]=0;
                        else if(lowcost[j]>e[v][j].cost)    lowcost[j]=e[v][j].cost;
                    }
                }
            }
        }
        cout<<spend<<endl;
    }
    return 0;
}


发表于 2020-03-24 19:35:49 回复(4)
#include<iostream>//并查集思想,此题有考虑到MST不存在的情况,要自己构造
#include<cmath>//先将flag=1的节点加入到集合中
#include<algorithm>
#include<iomanip>
using namespace std;
#define N 101
int tree[N];//保存各个节点的根节点
struct e//结构体保存两个节点之间的cost(距离,时间,建路建桥花费之类的)
{
	int a, b;
	int cost;
	bool flag;//此题有标志,1表示已修了路,0表示没修
	bool operator<(const e& s)const//重载<号
	{
		if (flag != s.flag)//flag=1的排在最前面,以便等下遍历的时候,把已经修了路的节点先放入一个集合中
			return flag > s.flag;
		else
		{
			if (cost != s.cost)//flag=0时,按照修路花费最少的排在前面
				return cost < s.cost;
			else//这个随便 你a小排前还是b小排前不影响
				return  a < s.a;
		}
	}
}edg[5500];
int findroot(int x)//找各个节点所在树的根节点
{
	if (tree[x] == -1)return x;//找到根节点,输出根节点
	else//路径压缩,比如1-2-3,3的祖先节点是2,进行路径压缩,递归查找2的祖先节点=1,
	{	//将3的祖先节点设为1,即1-2,1-3,此时1-3这条边的权值=以前2-3的权值,其实只是移动了边;
		int tmp = findroot(tree[x]);
		tree[x] = tmp;
		return tmp;
	}
}
int main()
{
	int n;
	while (cin >> n && n != 0)
	{
		for (int i = 1; i <= n; i++)//一开始的节点都是单独的连通分量
			tree[i] = -1;
		for (int i = 1; i <= n * (n - 1) / 2; i++)//输入数据
			cin >> edg[i].a >> edg[i].b >> edg[i].cost >> edg[i].flag;
		sort(edg + 1, edg + 1 + n * (n - 1) / 2);//排序
		int ans = 0;
		for (int i = 1; i <= n * (n - 1) / 2; i++)
		{
				int a = findroot(edg[i].a);//查找两个点的根节点
				int b = findroot(edg[i].b);
				if (a != b)//a!=b,说明两点不是在同一棵树上,要将其合并成同一个集合
				{
					tree[a] = b;// 将其合并成同一个集合,一棵树变成另一棵树根节点的子树
					if (edg[i].flag != 1)//判断,如果flag=1,则路已修不用再花费,反之,flag=0,要修路ans+=edg[i].cost,因为当flag=0时,我们的
						//edg数组是按cost的升序排列的,所以先修花费小的路,
					{
						ans += edg[i].cost;
					}
				}
		}//当所有的节点都在一棵树的时候,ans保持不变,直至循环退出,ans即为最小花费
		cout << ans << endl;
	}
	return 0;
}

编辑于 2020-03-20 00:32:33 回复(3)
/*思路很简单,先不考虑已经建造好的路,于是题目变成已知各边求最小生成树,很显然用克鲁斯卡尔算法就OK,又再考虑有已经建造好的路,因此这段路不需要再掏钱,所以当两点之间路已经建造好时,把费用设为0就行了*/

# include<stdio.h>
#
include<algorithm>
using namespace std;
const int maxn=110;
int father[maxn];
int result=0;
struct path {
    int a;   //路的起点
    int b;   //终点
    int d;   //花费
   
} p[5000];
 void Init(){    //初始化
     result=0;
     for(int i=0;i<maxn;i++){
         father[i]=i;
     }
 }
int findfather(int x){ //并查集找爹
    while(father[x]!=x)
        x=father[x];
    return x;
}
void Union(int x,int y){  //并查集合并
    int faA=findfather(x);
    int faB=findfather(y);
    father[faA]=faB;
   
}
bool cmp(path x,path y){  //克鲁斯卡尔需要给边排序
    return x.d<y.d;
}

int main(){
    int start,end,dis,flag;
    int i,j;
    int n;
    Init();
    while(scanf("%d",&n)!=EOF){
        if(n==0)
            break;
        int np=n*(n-1)/2;
        for(i=0;i<np;i++){
            scanf("%d%d%d%d",&start,&end,&dis,&flag);
            p[i].a=start;
            p[i].b=end;
            p[i].d=dis;
            if(flag==1)
                p[i].d=0;
        }
        sort(p,p+np,cmp);
        for(j=0;j<np;j++){
            if(findfather(p[j].a)!=findfather(p[j].b)){
                Union(p[j].a,p[j].b);
                result+=p[j].d;
            }
        }
        printf("%d\n",result);
        Init();
       
       
    }
   
    return 0;
}










发表于 2020-03-18 15:26:01 回复(0)
输入时将已修的路权值置为0
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn=10005;
int root[maxn];
struct edge
{
	int a,b,w,flag;
}r[maxn];
bool cmp(edge a,edge b)
{
	return a.w<b.w;
}
int getroot(int x)
{
	if(x!=root[x])
		root[x]=getroot(root[x]);
	return root[x];
}
int kruskal(int m)
{
	int ans=0;
	for(int i=0;i<maxn;++i)		root[i]=i;
	sort(r,r+m,cmp);
	for(int i=0;i<m;++i)
	{
		int ra=getroot(r[i].a), rb=getroot(r[i].b);
		if(ra!=rb)
		{
			root[ra]=rb;
			ans+=r[i].w;
		}
	}
	return ans;
}
int main()
{
	int n;
	while (cin>>n&&n)
	{
		int m=n*(n-1)/2;
		for(int i=0;i<m;++i)
		{
			cin>>r[i].a>>r[i].b>>r[i].w>>r[i].flag;
			if(r[i].flag)	r[i].w=0;			  //已修的路权值视为0
		}
		cout<<kruskal(m)<<endl;
	}
	return 0;
}

发表于 2020-01-12 20:24:05 回复(0)

在输入时就对已建好的路进行树的生成,且不保存,而后只保存未开始修建的路,然后进行排序和树的继续生成。

def findRoot(village):
    global villages
    if villages[village] == -1:
        return village
    else:
        temp = findRoot(villages[village])
        villages[village] = temp
        return temp
while True:
    try:
        n = int(input())
        if n == 0:
            break
        villages = [-1 for i in range(n+1)]
        nodeNum = 1
        roads = []
        for i in range(n*(n-1)//2):
            temp = list(map(int, input().split()))
            if temp[3] == 1:
                a = findRoot(temp[0])
                b = findRoot(temp[1])
                if a != b:
                    villages[a] = b
                    nodeNum += 1
            else:
                roads.append(temp)
        costs = 0
        roads.sort(key=lambda x:x[2])
        for i in range(len(roads)):
            a = findRoot(roads[i][0])
            b = findRoot(roads[i][1])
            if a != b:
                villages[a] = b
                costs += roads[i][2]
                nodeNum += 1
            if nodeNum == n:
                break
        print(costs)
    except Exception:
        break
编辑于 2018-09-28 16:01:24 回复(0)
最小生成树算法,排序函数稍微变一下,把已经建好的排在最前面,其余按cost递增排序,遍历边时遇到没建好的再加上cost。
#include<stdio.h>
#include<algorithm>
#define N 100
using namespace std;
typedef struct Edge{
    int a,b;
    int cost;
    int flag;
}Edge;
int Tree[N];
int FindRoot(int x){
    if(Tree[x]==-1) return x;
    else{
        Tree[x]=FindRoot(Tree[x]);
        return Tree[x];
    }
}
bool cmp(Edge a,Edge b){
    if(a.flag!=b.flag)
        return a.flag>b.flag;
    else
        return a.cost<b.cost;
}
int main(){
    int n;
    while(scanf("%d",&n)!=EOF&&n!=0){
        Edge e[N*(N-1)/2];
        int i;
        for(i=1;i<=n;i++)
            Tree[i]=-1;
        for(i=0;i<n*(n-1)/2;i++)
            scanf("%d%d%d%d",&e[i].a,&e[i].b,&e[i].cost,&e[i].flag);
        sort(e,e+n*(n-1)/2,cmp);
        int ans=0;
        int a,b;
        for(i=0;i<n*(n-1)/2;i++){
            a=FindRoot(e[i].a);
            b=FindRoot(e[i].b);
            if(a!=b){
                Tree[b]=a;
                if(e[i].flag==0)
                    ans+=e[i].cost;
            }
        }
        printf("%d\n",ans); 
    }
    return 0;
}

发表于 2018-01-21 22:14:18 回复(0)
两种思路,一种是如果为1 则代价为0 其余相同
另一种是如果为1 则合并两个点 不加入排序
发表于 2019-05-15 15:47:44 回复(0)
#include<bits/stdc++.h>
using namespace std;

const int Max=100;

struct Edge {
    int f;
    int t;
    int c;
    bool operator < (const Edge & a) {
        return c<a.c;
    }
};

Edge edge[Max*Max];
int father[Max];
int height[Max];
bool status[Max*Max];

void Initial(int n) {
    for(int i=0; i<=n; i++) {
        father[i]=i;
        height[i]=0;
    }
    return ;
}

int Find(int x) {
    if(x!=father[x]) {
        father[x]=Find(father[x]);
    }
    return father[x];
}

void Union(int x,int y) {
    x=Find(x);
    y=Find(y);
    if(x!=y) {
        if(height[x]>height[y]) {
            father[y]=x;
        } else if(height[x]<height[y]) {
            father[x]=y;
        } else {
            father[y]=x;
            height[x]++;
        }
    }
    return ;
}

int Kruskal(int n) {
    sort(edge,edge+n*(n-1)/2);
    int sum=0;
    for(int i=0; i<n*(n-1)/2; i++) {
        if(Find(edge[i].f)!=Find(edge[i].t)) {
            Union(edge[i].f,edge[i].t);
            sum+=edge[i].c;
        }
    }
    return sum;
}

int main() {
    int n;
    while(cin>>n) {
        Initial(n);
        for(int i=0; i<n*(n-1)/2; i++) {
            cin>>edge[i].f>>edge[i].t>>edge[i].c>>status[i];
            if(status[i]) {
                edge[i].c=0;
            }
        }
        cout<<Kruskal(n)<<endl;
    }
    return 0;
}
发表于 2022-10-13 19:27:30 回复(1)
因为按是否修好路排序所以可能会出现已经在并查集中的节点又有修好的路,这种边不能算进最终的边数
#include <cstdio>
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
int father[10001];
void InitSet(int n) {
    for (int i = 0; i < n; i++) {
        father[i] = i;
    }
}
int FindFather(int u) {
    if (u == father[u]) {
        return u;
    } else {
        father[u] = FindFather(father[u]);
        return father[u];
    }
}
void UnionSet(int r, int t) {
    r = FindFather(r);
    t = FindFather(t);
    father[r] = t;
}
struct edge {
    int u;//边的两个结点、权值
    int v;
    int w;
    int live;//表示是否修建
    edge(int _u, int _v, int _w, int _live) {
        u = _u;
        v = _v;
        w = _w;
        live = _live;
    }
};
bool cmp(edge a, edge b) {
    if (a.live != b.live) {
        return a.live >
               b.live; //不花费钱的优先级最高先加入到并查集,从小到大最小生成树
    } else {
        return a.w < b.w;
    }
}
int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        vector<edge> edgev;//存储边的向量
        if (0 == n) {
            break;
        }
        for (int i = 0; i < (n - 1)*n / 2; i++) {
            int u, v, w, live;
            scanf("%d %d %d %d", &u, &v, &w, &live);
            edge e(u, v, w, live);
            edgev.push_back(e);
        }
        InitSet(10001);
        sort(edgev.begin(), edgev.end(), cmp);
        int edgenum = 0, totaledgesum = 0; //边的数量与权值之和
        for (int i = 0; i < edgev.size(); i++) {
            int u = edgev[i].u, v = edgev[i].v, w = edgev[i].w, live = edgev[i].live;
            if (1 == live) {
                if (FindFather(u) != FindFather(v)) {
                    edgenum++; //只有不在同一个并查集的已有路才算边
                }
                UnionSet(u, v);
            } else {
                if (FindFather(u) != FindFather(v)) {
                    edgenum++;
                    UnionSet(u, v);
                    totaledgesum += w;
                }
            }
            if (edgenum == n - 1) { //最小树已经生成完毕
                break;
            }
        }
        printf("%d\n", totaledgesum);

    }
}
// 64 位输出请用 printf("%lld")

发表于 2025-03-22 10:48:55 回复(0)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>

using namespace std;

const int MAXN = 100;

struct Edge {
    int form;
    int to;
    int length;
};

int father[MAXN];
int height[MAXN];
vector<Edge> edge1(MAXN* MAXN);

bool operator < (Edge lhs, Edge rhs) {
    return lhs.length < rhs.length;
}

void Init(int n) {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
        height[i] = 0;
    }
}

int Find(int u) {
    if (father[u] == u) {
        return u;
    } else {
        father[u] = Find(father[u]);
    }
    return father[u];
}

void Union(int u, int v) {
    int uroot = Find(u);
    int vroot = Find(v);
    if (uroot != vroot) {
        if (height[u] < height[v]) {
            father[uroot] = vroot;
        } else if (height[u] > height[v]) {
            father[vroot] = uroot;
        } else {
            father[vroot] = uroot;
            height[u]++;
        }
    }
}

int Kruskal(int n, int edgeNumber) {
    Init(n);
    sort(edge1.begin(), edge1.begin() + edgeNumber);
    int sum = 0;
    for (int i = 0; i < edgeNumber; ++i) {
        Edge current = edge1[i];
        if (Find(current.form) != Find(current.to)) {
            Union(current.form, current.to);
            sum += current.length;
        }
    }
    return sum;
}

int main() {
    int n;
    while (cin >> n) {
        if (n == 0) {
            break;
        }
        int edgeNumber = n * (n - 1) / 2;
        int statue;
        for (int i = 0; i < edgeNumber; ++i) {
            cin >> edge1[i].form >> edge1[i].to >> edge1[i].length >> statue;
            if (statue == 1) {
                edge1[i].length = 0;
            }
        }
        int sum = Kruskal(n, edgeNumber);
        cout << sum << endl;
    }
    return 0;
}

发表于 2025-03-19 18:33:57 回复(0)
Kruskal算法构建最小生成树。使用vector<struct>保存每条道路信息,然后对道路的成本升序排序。排序完成后,设置并查集表示城镇目前的连通情况,将输入里已经修建的城镇道路并入并查集。然后开始修路,修路条数=并查集中连通分支的个数-1。然后在还没修的道路中按cost从小到大的顺序依次并入并查集即可。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct road{
	int townA;
	int townB;
	int cost;
	bool build;
};
bool compare(road lhs,road rhs){
	return lhs.cost<rhs.cost;
}
int findset(vector<int> &vec,int a){
	while(vec[vec[a]]!=vec[a]){//路径压缩,如果a父亲不是根结点,则无限循环直到找到根结点
		vec[a]=vec[vec[a]];
	}
	return vec[a];
}
void unionset(vector<int> &vec, int a, int b) {
    int rootA = findset(vec, a);
    int rootB = findset(vec, b);
    if (rootA != rootB) {
        vec[rootA] = rootB;
    }
}
int main(){
	int n;//n表示村庄数目
	while(cin >> n && n!=0){
		vector<road> vec(n*(n-1)/2+1);//存放道路情况
		for(int i=1;i<=(n*(n-1)/2);i++){//输入道路情况
			cin >> vec[i].townA >> vec[i].townB >> vec[i].cost >> vec[i].build;
		}
		vector<int> connected_set(n+1);//并查集
		for(int i=1;i<=n;i++){
			connected_set[i]=i;//初始化,每个集合元素的父结点都是自己
		}
		//kruskal算法
		sort(vec.begin()+1,vec.end(),compare);//把所有道路按照从小到大排序
		for(int i=1;i<=n*(n-1)/2;i++){
			if(vec[i].build==true){//将已有道路的连接到并查集
				unionset(connected_set,vec[i].townA,vec[i].townB);
			}
		}
		int need=-1;//还需修建need条路
		for(int i=1;i<=n;i++){
			if(findset(connected_set,i)==i){
				need++;
			}
		}
		int money=0;
		for(int i=1;i<=n*(n-1)/2 && need>0;i++){
			//如果没有修建过且未连通,则修建
			if(!vec[i].build && (findset(connected_set,vec[i].townA)!=findset(connected_set,vec[i].townB))){
				vec[i].build=true;
				unionset(connected_set,vec[i].townA,vec[i].townB);
				money+=vec[i].cost;
				need--;
			}
		}
		cout << money << endl;
	}
}

发表于 2025-03-19 16:50:54 回复(0)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int village[100];
struct edge{
    int start;
    int end;
    int fare;
    int flag;
};
void Initset(int n){
     for(int i=0;i<n;i++){
         village[i]=i;
     }
}
bool compare(edge lhs,edge rhs){
    if(lhs.flag>rhs.flag){
        return true;
    }
    else if(lhs.flag==rhs.flag&&(lhs.fare<rhs.fare)){
        return true;
    }
    else return false;
}
int findfather(int n){
    if(village[n]==n){
        return n;
    }
    else return findfather(village[n]);
}
int main(){
    int n;
    while(cin>>n) {
        if(n==0){
            break;
        }
        vector<edge> v(n * (n - 1 / 2));
//        int village[n];
        Initset(n + 1);//初始化并查集
        for (int i = 0; i < n * (n - 1) / 2; i++) {
            cin >> v[i].start >> v[i].end >> v[i].fare >> v[i].flag;
        }
        sort(v.begin(), v.end(), compare);
        vector<edge>::iterator it;
        int weight = 0;
        int setnode = n;
        for (it = v.begin(); it != v.end(); it++) {
            int a = (*it).start;
            int b = (*it).end;
            int aroot = findfather(a);
            int broot = findfather(b);
            if (aroot != broot) {
                village[broot] = aroot;
                setnode--;
                if ((*it).flag == 0) {
                    weight += (*it).fare;
                }
                if (setnode == 1) {
                    break;
                }
            }
        }
        cout << weight << endl;
    }
    return 0;
}



发表于 2025-02-20 00:31:34 回复(0)
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;

void initial(vector<int> &set)
{
    for (int i = 0; i < set.size(); i++)
    {
        set[i] = i;
    }
}

int Find(int aim, vector<int> &set)
{
    if (set[aim] == aim)
    {
        return aim;
    }
    else
    {
        int root = Find(set[aim], set);
        set[aim] = root;
        return root;
    }
}

void Union(int u, int v, vector<int> &set)
{
    int uroot = Find(u, set);
    int vroot = Find(v, set);

    set[uroot] = vroot;
}

struct Edge
{
    int start;
    int dest;
    int weight;

    Edge(int _start, int _dest, int _weight)
    {
        start = _start;
        dest = _dest;
        weight = _weight;
    }
};

bool compare(struct Edge lhs, struct Edge rhs)
{
    if (lhs.weight < rhs.weight)
    {
        return true;
    }
    else
    {
        return false;
    }
}

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        if (n == 0)
        {
            break;
        }
        vector<int> set(n + 1);
        vector<Edge> path;
        initial(set);
        int begin, end, weight, flag;
        for (int i = 0; i < n * (n - 1) / 2; i++)
        {
            scanf("%d %d %d %d", &begin, &end, &weight, &flag);
            if (flag == 1)
            {
                Edge e(begin, end, 0);
                path.push_back(e);
            }
            else
            {
                Edge e(begin, end, weight);
                path.push_back(e);
            }
        }
        sort(path.begin(), path.end(), compare);
        int prize = 0;
        int count = n;
        for (int i = 0; i < path.size(); i++)
        {
            int start = path[i].start;
            int end = path[i].dest;
            int weight = path[i].weight;

            if (Find(start, set) != Find(end, set))
            {
                Union(start, end, set);
                prize += weight;
                count--;
            }
            if (1 == count)
            {
                break;
            }
        }
        printf("%d\n", prize);
    }
    return 0;
}


发表于 2025-02-10 00:50:55 回复(0)