Aacwing练习代码汇总

动态规划

背包问题

01背包

#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    
    for(int i = 1; i <= n; i++) 
        for(int j = m; j >= v[i]; j--) 
            f[j] = max(f[j], f[j-v[i]]+w[i]);
            
    cout << f[m] << endl;
	return 0;    
}

完全背包

#include<iostream>
using namespace std;

const int N = 1010;

int n, m;
int dp[N];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ){
        int v, w;
        cin >> v >> w;
        for(int j = v; j <= m; j ++ ){
                dp[j] = max(dp[j], dp[j - v] + w);
        }
    }
    cout << dp[m] << endl;
}

多重背包

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e2+10;
int  f[N],w[N],v[N],s[N];
int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=V;j>=0;j--)
        {
            for(int k=0;k<=s[i];k++)
            {
                if(k*v[i]<=j) f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<f[V];
    return 0;
}

分组背包

#include<bits/stdc++.h>
using namespace std;

const int N=110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m,k;

int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>s[i];
        for(int j=0;j<s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }

    for(int i=0;i<n;i++){
        for(int j=m;j>=0;j--){
            for(int k=0;k<s[i];k++){    //for(int k=s[i];k>=1;k--)也可以
                if(j>=v[i][k])     f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout<<f[m]<<endl;
}

图论

DFS

排列数字

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式:共一行,包含一个整数 n。
#include<bits/stdc++.h>
using namespace std;

int n; 
int nums[10];
bool visited[10];

void dfs(int u){
    if(u == n){
        for(int j = 0;j<n;j++) cout << nums[j] << " ";
        cout<< endl;
        return;
    }
    for(int i = 1; i <= n; i++){
        //找到一个还没有被用过的数字
        if(visited[i] == false){
            nums[u] = i;
            visited[i] = true;
            //dfs(i+1)后,可以理解为已经默认完成了由此为起点的向下深搜的行为;
            //接下来是回溯代码(回复现场)
            dfs(u+1);//向下
            visited[i] = false;//后再向上返回
        }
    }
}

int main(){
    
    cin >> n;
    dfs(0);
    
    return 0;
}

八皇后问题

/*
n− 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋	盘上,
使得皇后不能相互攻击到,即任意两个皇后都不	
能处于同一行、同一列或同一斜线上。
	输入样例:
	4
	输出样例:
	.Q..
	...Q
	Q...
	..Q.
	
	..Q.
	Q...
	...Q
	.Q..
*/

#include<bits/stdc++.h>
using namespace std;
const int N = 20;

int n; 
char ans[N][N];
int col[N],dg[N],udg[N];

void dfs(int u){
    if(u == n) {
        for(int i = 0; i < n; i++) puts(ans[i]);
        puts("");
        return;
        
    }
    
    for(int i = 0; i < n; i++){
        if(!col[i] && !dg[u+i] && !udg[n + i -u]){
            ans[u][i] = 'Q';
            col[i] = dg[u + i] =udg[n + i -u] = true;
            dfs(u+1);
            col[i] = dg[u + i] =udg[n + i -u] = false;
            ans[u][i] = '.';
        }
        
    }
}


int main(){
    cin >> n;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n;j++)
            ans[i][j] = '.';
    dfs(0);
    
    return 0;
}

树与图的DFS

树的重心

/*
给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式
第一行包含整数n,表示树的结点数。

接下来n-1行,每行包含两个整数a和b,表示点a和点b之前存在一条边。

输出格式
输出一个整数m,表示重心的所有的子树中最大的子树的结点数目。

数据范围
1≤n≤105

样例
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4

*/

#include <iostream>
#include <cstring>
using namespace std;

const int N=100010;
bool state[N];

//因为是双向边
int h[N],e[2*N],ne[2*N],idx,ans=N;
int n;
int add(int a,int b){
   e[idx]=b;
   ne[idx]=h[a];
   h[a]=idx++;
}

//返回的是以u为根的子树中点的数量
int dfs(int u){
    //标记u这个点被搜过
    state[u]=true;
    //size是表示将u点去除后,剩下的子树中数量的最大值;
    //sum表示以u为根的子树的点的多少,初值为1,因为已经有了u这个点
    int size=0,sum=1;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(state[j]) continue;
        //s是以j为根节点的子树中点的数量
        int s=dfs(j);
        //
        size=max(size,s);
        sum+=s;
    }
    //n-sum表示的是减掉u为根的子树,整个树剩下的点的数量
    size=max(size,n-sum);
    ans=min(size,ans);
    return sum;
}

int main(){
    cin>>n;
    memset(h,-1,sizeof h);
    int a,b;
    for(int i=0;i<n;i++){
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    dfs(1);
    cout<<ans;
    return 0;
}

BFS

走迷宫

#include<bits/stdc++.h>
using namespace std;

/*
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
*/

const int N = 110;

int g[N][N];
int v[N][N];//visit数组中顺便放入bfs到此处的路径最短长度
queue<pair<int,int>> q;

int main() {
	int m, n;
	cin >> m >> n;
	for(int i = 0; i <m;i++ )
		for (int j = 0; j < n; j++) {
			cin >> g[i][j];
		}

	memset(v, -1, sizeof(v));
	v[0][0] = 0;

	q.push({0,0});
	



	int x, y;
	int dx[4] = { -1,0,1,0 }; int dy[4] = { 0,1,0,-1 };
	
	while (!q.empty()) {
		//pair<int,int> f = q.pop(); // 经典错误:出队时并不会返回元素;
		auto f = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			x = f.first + dx[i];
			y = f.second + dy[i];
			if (x>=0&&x < m && y>=0 && y < n && g[x][y] == 0 && v[x][y] ==-1) {
				q.push({ x,y });
				v[x][y] = v[f.first][f.second] + 1;
			}
		}
	}

	cout<<  v[m - 1][n - 1];
}

八数码



/*
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中
在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 x

*/




#include<bits/stdc++.h>
using namespace std;


int bfs(string start) {
	queue<string> q;
	unordered_map<string, int> d;

	q.push(start);
	d[start] = 0;


	int dx[4] = { -1,0,1,0 }; int dy[4] = { 0,1,0,-1 };

	while (!q.empty()) {
		string f = q.front();
		q.pop();
		int from = d[f];


		if (f == "12345678x") return d[f];
		
		int k = f.find('x');
		int x = k / 3, y = k % 3;  // 一维下标转二维下标
		for (int i = 0; i < 4; i++) {
			int a = x + dx[i], b = y + dy[i];

			if (a >= 0 && a < 3 && b >= 0 && b < 3) {
				swap(f[k], f[a * 3 + b]); // 二维下标转一维坐标
				if (d.count(f) == 0) {
					d[f] = from + 1;
					q.push(f);
				}
				swap(f[k], f[a * 3 + b]);
			}
		}


	}

	return -1;
}

int main() {
	string start;
	for (int i = 0; i < 9; i++) {
		char c;
		cin >> c;
		start += c;
	}

	cout << bfs(start) << endl;

	return 0;


}

树与图的BFS

图中点的层次

#include <cstring>
#include <iostream>

using namespace std;

const int N=1e5+10;

int h[N], e[N], idx, ne[N];
int d[N]; //存储每个节点离起点的距离  d[1]=0
int n, m; //n个节点m条边
int q[N]; //存储层次遍历序列 0号节点是编号为1的节点

void add(int a, int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int bfs()
{
    int hh=0,tt=0;

    q[0]=1; //0号节点是编号为1的节点

    memset(d,-1,sizeof d);

    d[1]=0; //存储每个节点离起点的距离

    //当我们的队列不为空时
    while(hh<=tt)
    {
        //取出队列头部节点
        int t=q[hh++];

        //遍历t节点的每一个邻边
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            //如果j没有被扩展过
            if(d[j]==-1)
            {
                d[j]=d[t]+1; //d[j]存储j节点离起点的距离,并标记为访问过
                q[++tt] = j; //把j结点 压入队列
            }
        }
    }

    return d[n];
}

int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }

    cout<<bfs()<<endl;
}

拓扑排序

/*
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

输入格式
第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1。

数据范围
1≤n,m≤105
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3
*/


#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100010;
int h[N],e[N],ne[N],idx;
int n,m;
int q[N],d[N];//q表示队列,d表示点的入度

void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

bool topsort()
{
    int hh=0,tt=-1;
    for(int i=1;i<=n;i++)
     if(!d[i]) 
     q[++tt]=i;//将入度为零的点入队
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            d[j]--;//删除点t指向点j的边
            if(d[j]==0)//如果点j的入度为零了,就将点j入队
            q[++tt]=j;
        }
    }
    return tt==n-1;
    //表示如果n个点都入队了话,那么该图为拓扑图,返回true,否则返回false
}

int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof(h));//如果程序时间溢出,就是没有加上这一句
    for(int i=0;i<m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);//因为是a指向b,所以b点的入度要加1
        d[b]++;
    }
    if(topsort()) 
    {
        for(int i=0;i<n;i++)
        printf("%d ",q[i]);
        //经上方循环可以发现队列中的点的次序就是拓扑序列
        //注:拓扑序列的答案并不唯一,可以从解析中找到解释
        puts("");
    }
    else
    puts("-1");

    return 0;
}



最短路相关

在这里插入图片描述

Dijkstra 单源最短路径

思路:
1、初始化dist距离数组:dist[0] = 1; dist[i] = +∞; s数组存放当前已经确定的最小路上的点
2、for i : 0-n
取t <—非s中的距离最小的点。找人(非组织中的最小值)
s<–t <—将t放入s中。加入组织
用t更新更新dist (看t出边: t–>x 的val值是否满足 d[t]+val < d[x]) 。同化

// tip:朴素的dijkstra算法的边很多,故使用邻接矩阵存贮该稠密图

/*
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式
第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围
1≤n≤500,
1≤m≤105,
图中涉及边长均不超过10000。

输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
*/

#include<bits/stdc++.h>
using namespace std;

const int N = 510;
int g[N][N];
bool s[N];
int d[N];
int n, m;

int Dijkstra(int goal) {
    
    memset(d, 0x3f3f3f3f, sizeof d);
	d[1] = 0;  // 将题中的起点到起点的距离设为0 注意不是d[0] = 0; 此时的d[0] = 0x3f3f3f3f

	for (int i = 0; i < n; i++) {
		int t = 0;
		
		//找人
		for (int j = 1; j <= n; j++) //j不是下标,是序号数
			if (!s[j] && d[j] < d[t]) 
				t = j;
		//拉入组织
		s[t] = true;
		//同化
		for (int j = 1; j <= n; j++) {
			if (d[t] + g[t][j] < d[j]) 
				d[j] = d[t] + g[t][j];
		}

	}


	if (d[n] == 0x3f3f3f3f) return -1;
	return d[goal];
}

int main() {
	 cin >> n >> m;
	int x, y, z;



	//memset(s, false, sizeof s);
	
	memset(g, 0x3f3f3f3f, sizeof g);
	for (int i = 0; i < m; i++) {
		cin >> x >> y >> z;
		g[x][y] = min(g[x][y], z); //删除重边的影响
	}

	cout << Dijkstra(n) << endl;
	return 0;
}

bellman-ford

// 有边数限制的最短路
思路:
1)初始化所有点到源点的距离为∞,把源点到自己的距离设置为0;
2)无论三七二十一先遍历n次;每次遍历m条边,用每一条边去更新各点到源点的距离。

/*
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。

注意:图中可能 存在负权回路 。

输入格式
第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible。

数据范围
1≤n,k≤500,
1≤m≤10000,
任意边长的绝对值不超过 10000。

输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
*/

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510,M=10010;

struct Edge {//只需要将边遍历一遍,用结构体数组储存即可
    int a, b, c;
} edges[M];

int n, m, k;
int dist[N],last[N];

void bellman_ford(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    for(int i=0;i<k;i++){//保证第k次时,dist[n]是走过k步及以内的最短路。第k+5次时dist[n]的最短路第k+5次时再确定!!!
        memcpy(last,dist,sizeof dist);//保留上一次迭代的dist的副本,防止跨层数更新

        for(int j=0;j<m;j++){
            auto t=edges[j];
            dist[t.b]=min(dist[t.b],last[t.a]+t.c);//用已确定最短路的点向外延申,类似于dijkstra
        }
    }
}

int main() {
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        edges[i] = {x, y, z};
    }

    bellman_ford();

    if(dist[n]>0x3f3f3f3f/2)//0x3f3f3f3f并非真正的无穷大,会随着dist数值更新而受到影响
        cout<<"impossible";//输出字符串或数字,不能用三目运算符
    else cout<<dist[n];
}


Floyd

/*
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible。

数据保证图中不存在负权回路。

输入格式
第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。

输出格式
共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。

数据范围
1≤n≤200,
1≤k≤n2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。

输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1
*/

#include <iostream>
using namespace std;

const int N = 210, M = 2e+10, INF = 1e9;

int n, m, k, x, y, z;
int d[N][N];

void floyd() {
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main() {
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(i == j) d[i][j] = 0;
            else d[i][j] = INF;
    while(m--) {
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z);
        //注意保存最小的边
    }
    floyd();
    while(k--) {
        cin >> x >> y;
        if(d[x][y] > INF/2) puts("impossible");
        //由于有负权边存在所以约大过INF/2也很合理
        else cout << d[x][y] << endl;
    }
    return 0;
}

最小生成树相关

Prim

思路:
int dist[n],state[n],pre[n];
dist[1] = 0;
for(i : 1 ~ n)	{
    t <- 没有连通起来,但是距离连通部分最近的点;
    state[t] = 1;
    更新 dist 和 pre;
}
/*
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式
第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

数据范围
1≤n≤500,
1≤m≤105,
图中涉及边的边权的绝对值均不超过 10000。

输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6


#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510;
int g[N][N];//存储图
int dt[N];//存储各个节点到生成树的距离
int st[N];//节点是否被加入到生成树中
int pre[N];//节点的前去节点
int n, m;//n 个节点,m 条边

void prim()
{
    memset(dt,0x3f, sizeof(dt));//初始化距离数组为一个很大的数(10亿左右)
    int res= 0;
    dt[1] = 0;//从 1 号节点开始生成 
    for(int i = 0; i < n; i++)//每次循环选出一个点加入到生成树
    {
        int t = -1;
        for(int j = 1; j <= n; j++)//每个节点一次判断
        {
            if(!st[j] && (t == -1 || dt[j] < dt[t]))//如果没有在树中,且到树的距离最短,则选择该点
                t = j;
        }

        st[t] = 1;// 选择该点
        res += dt[t];
        for(int i = 1; i <= n; i++)//更新生成树外的点到生成树的距离
        {
            if(dt[i] > g[t][i] && !st[i])//从 t 到节点 i 的距离小于原来距离,则更新。
            {
                dt[i] = g[t][i];//更新距离
                pre[i] = t;//从 t 到 i 的距离更短,i 的前驱变为 t.
            }
        }
    }
}

void getPath()//输出各个边
{
    for(int i = n; i > 1; i--)//n 个节点,所以有 n-1 条边。

    {
        cout << i <<" " << pre[i] << " "<< endl;// i 是节点编号,pre[i] 是 i 节点的前驱节点。他们构成一条边。
    }
}

int main()
{
    memset(g, 0x3f, sizeof(g));//各个点之间的距离初始化成很大的数
    cin >> n >> m;//输入节点数和边数
    while(m --)
    {
        int a, b, w;
        cin >> a >> b >> w;//输出边的两个顶点和权重
        g[a][b] = g[b][a] = min(g[a][b],w);//存储权重
    }

    prim();//求最下生成树
    //getPath();//输出路径
    return 0;
}

*/

Kruskal

思路:(解决稀疏图)
将所有边按照权值的大小进行升序排序,然后从小到大一一判断。
如果这个边与之前选择的所有边不会组成回路,就选择这条边分;反之,舍去。
直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。
筛选出来的边和所有的顶点构成此连通网的最小生成树。
判断是否会产生回路的方法为:使用并查集。
在初始状态下给各个个顶点在不同的集合中。、
遍历过程的每条边,判断这两个顶点的是否在一个集合中。
如果边上的这两个顶点在一个集合中,说明两个顶点已经连通,这条边不要。如果不在一个集合中,则要这条边。
/*
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式
第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

数据范围
1≤n≤105,
1≤m≤2∗105,
图中涉及边的边权的绝对值均不超过 1000。

输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
*/


#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int p[N];

struct Edge {
    int a, b, w;

    bool operator<(const Edge &e) const {
        return w < e.w;
    }
} es[M];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal() {
    int cnt = 0, res = 0;

    sort(es, es + m);

    for (int i = 1; i <= n; i++) p[i] = i;

    for (int i = 0; i < m; i++) {
        int a = es[i].a, b = es[i].b, w = es[i].w;
        a = find(a), b = find(b);
        if (a != b) {
            p[a] = b;
            res += w;
            cnt++;
        }
    }

    if (cnt < n - 1) return INF;
    else return res;
}

int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        es[i] = {a, b, w};
    }

    int t = kruskal();
    if (t == INF) cout << "impossible";
    else cout << t;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值