文章目录
动态规划
背包问题
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;
}