Acwing 第三章 搜索与图论
基础课第三章 搜索与图论:DFS、BFS、树与图的优先遍历、拓扑排序、各个最短路、最小生成树、二分图
一、DFS
DFS中有两个重要的概念:回溯和剪枝
当图中所有边的权重为1时,BFS搜到的一定是最短路
回溯时一定要注意恢复现场
排列数字
#include<iostream>
using namespace std;
const int N = 10;
int n;
int path[N];//记录所有的搜索路径
bool st[N];//记录这些点有没有被用过,1表示是,0表示否
void dfs(int u) // 第u层
{
if(u == n)//从0开始作为第一层,当搜索完最后一层,就输出这条路径并结束递归
{
for(int i=0;i<n;i++)
{
printf("%d ",path[i]);
}
puts("");
return;
}
for(int i=1;i<=n;i++)
{
if(!st[i])
{
path[u] = i;//写入路径记录
st[i] = true;//更新状态为已使用
dfs(u+1);//给下一层找数
//----------------------------------下一层递归结束,此时该恢复状态了
st[i] = false;//更新状态为未使用
path[u] = 0;//清空该层路径记录
}
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
n皇后问题
剪枝:提前判断,当前方案一定不合法时,没有必要往下搜,直接回溯
图中一共有16条正对角线,16条反对角线
正对角线:斜率为-1的且经过该点的直线
反对角线:斜率为1的且经过该点的直线
搜法1:按行搜索
#include<iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N];//记录方案
bool col[N],dj[2*N],fdj[2*N];//同一行(列)、正对角线、反对角线
void dfs(int u)
{
if(u == n)//搜完最后一行,叶结点时,输出方案
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
printf("%c",g[i][j]);
}
puts("");
}
puts("");
return;
}
for(int i = 0;i < n;i++)//从第一列 x开始枚举 i:x u:y
{
if(!col[i] && !dj[i+u] && !fdj[n + u - i])//防止反对角为负数,加n到正常范围
{
g[u][i] = 'Q';//放置皇后
col[i] = dj[i+u] = fdj[u-i+n] = true;//状态更新
dfs(u+1);//搜索下一列
//恢复状态,为回溯做准备
g[u][i] = '.';
col[i] = dj[i+u] = fdj[u-i+n] = false;
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
g[i][j] = '.';//初始化
}
}
dfs(0);
return 0;
}
搜法2:按点从左到右,从上到下搜,枚举放与不放
#include<iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N];//记录方案
bool row[N],col[N],dj[2*N],fdj[2*N];//同一行(列)、正对角线、反对角线
void dfs(int x,int y,int cnt)
{
if(y == n)
{
++x,y = 0;
}
if(x==n)
{
if(cnt == n)
{
for (int i = 0; i < n; i ++ ) puts(g[i]);
puts("");
}
return;
}
dfs(x,y+1,cnt); //不放
if(!col[x] && !row[y] && !dj[x+y] && !fdj[y-x+n]) //放
{
g[x][y] = 'Q';
col[x] = row[y] = dj[x+y] = fdj[y-x+n] = true;
dfs(x,y+1,cnt+1);
g[x][y] = '.';//记得恢复现场
col[x] = row[y] = dj[x+y] = fdj[y-x+n] = false;
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
g[i][j] = '.';//初始化
}
}
dfs(0,0,0);
return 0;
}
二、BFS
走迷宫
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 105;
typedef pair<int,int> PII;
queue<PII>q;
int n,m;
int g[M][M];
int cnt[M][M];//存储走到每个点时经过的步数
int dx[4] = {0,0,-1,1};//x方向
int dy[4] = {1,-1,0,0};//y方向:上下左右
int bfs()
{
q.push({0,0});
cnt[0][0] = 0;
while(!q.empty())
{
PII pos = q.front();
q.pop();
for(int i=0;i<4;i++)
{
int y = pos.first + dy[i];
int x = pos.second + dx[i];
if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //范围内,不是墙,且没走过
{
q.push({y,x});
cnt[y][x] = cnt[pos.first][pos.second] + 1; //该点的步数等于上一点的步数加1
}
}
}
return cnt[n-1][m-1];//返回出口的点的步数
}
int main()
{
cin>>n>>m;//对于边权重全为1的图,bfs可以找到最短路,但是不能走回头路(一个点走两次)
memset(cnt,-1,sizeof cnt);//初始化cnt数组为-1,表示没走过该点
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>g[i][j];
}
}
cout<<bfs()<<endl;
return 0;
}
输出路径
if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //范围内,不是墙,且没走过
{
q.push({y,x});
cnt[y][x] = cnt[pos.first][pos.second] + 1; //该点的步数等于上一点的步数加1
Prev[y][x] = pos;
}
PII Prev[M][M];
while(i || j)
{
cout<<i<<" "<<j<<endl;
pos = Prev[i][j];
i = pos.first;
j = pos.second;
}
845. 八数码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;
int bfs(string start)
{
int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};
unordered_map<string,int>dist;//存储从起点开始交换的次数
dist[start] = 0;//初始化起点次数为0
string right = "12345678x"; //最终结果
queue<string>q;
q.push(start);
while(!q.empty())
{
string state = q.front();q.pop();
int dis = dist[state];
if(state == right) return dis; //找到结果了,返回次数
int pos = state.find('x');//找到x的一维位置
int x = pos/3,y = pos % 3;//转化为二维位置
for (int i = 0; i < 4; i ++ ) //枚举邻接点
{
int nx = x + dx[i],ny = y + dy[i];
if(nx<0||nx>=3||ny<0||ny>=3) continue;
swap(state[pos],state[nx*3+ny]);//交换产生新状态
if(!dist.count(state)) //不是之前遇到的状态,就入队列
{
q.push(state);
dist[state] = dis + 1; //记得维护距离
}
swap(state[pos],state[nx*3+ny]); //注意:此处一定要恢复现场,保证后面的状态能正确枚举
}
}
return -1; //没有搜到最终状态
}
int main()
{
string str;
char c;
for (int i = 0; i < 9; i ++ )
{
cin>>c; //这样写不会把空格读进去
str += c;
}
//cout<<str<<endl;
cout<<bfs(str)<<endl;
return 0;
}
三、树与图的存储
边多用矩阵,点多用表
邻接表一般在插入时选择头插法
数组模拟链表
e[]相当于val,ne[]相当于next
四、树与图的优先遍历
树的重心
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int h[N],e[2*N],ne[2*N],idx;
bool st[N];
int n;
int ans = N;//答案:最大子树的最小值
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int dfs(int u)
{
st[u] = true;
int sum = 1;//当前子树u的大小
int res = 0;//删除该点后,每一个连通块中的点的数量的最大值
for(int i = h[u];i!=-1;i = ne[i])
{
int j = e[i];
if(!st[j])
{
int s = dfs(j);//当前这一棵子树j的大小
res = max(res,s);
sum += s;//子树u的大小等于组成其的各子树之和
}
}
res = max(res,n-sum);//和父结点所在的子树比较
ans = min(ans,res);//和答案比较,更新答案
return sum;//返回的是子树的大小
}
int main()
{
memset(h,-1,sizeof h);
cin>>n;
for(int i=0;i<n;i++)
{
int a,b;
cin>>a>>b;
add(a,b),add(b,a);//边是双向的
}
dfs(1);
cout<<ans<<endl;
}
BFS图中点的层次
经典BFS
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int M = 1e5+10;
int h[M],e[M],ne[M],idx;
int n,m;
int d[M];//记录该点与起点的距离
queue<int>q;
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
int bfs()
{
q.push(1);
memset(d,-1,sizeof d);
d[1] = 0;//起点距离为0
while(!q.empty())
{
int t = q.front();
q.pop();
for(int i = h[t];i != -1;i = ne[i])
{
int j = e[i];
if(d[j] == -1)//距离为-1说明之前没有遍历过
{
q.push(j);
d[j] = d[t] + 1;//更新距离,相邻点的距离就加1
}
}
}
return d[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);//注意:这里一定要初始化链表头为-1,否则TLE
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);//单向边:a到b
}
cout<<bfs()<<endl;
return 0;
}
五、拓扑序列
有向无环图一定存在拓扑序列,也被称为拓扑图
入度:有多少条边指向该结点
出度:该结点有几个指向的结点(几条边)
如果有环,环中的所有点不会入队,因为找不到突破口
848. 有向图的拓扑序列
注意:此题答案不唯一,输出合法拓扑序即可
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M = 1e5 + 10;
int e[M],ne[M],h[M],idx;
int d[M],q[M],hh=0,tt=-1;//d[]是每个点的入度,注意:数组模拟队列,hh为0
int n,m,x,y;
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx ++;
d[b]++;
}
bool TopSort()//bfs思路
{
for(int i=1;i<=n;i++)//先把图中所有度为0的点入队
{
if(!d[i]) q[++tt] = i;
}
while(tt >= hh)//队列不空
{
int t = q[hh++];//出队
for(int i=h[t];i!=-1;i = ne[i])//遍历该点所有指向的点
{
int tmp = e[i];
d[tmp]--;//删掉指向该点的一条边,入度减一
if(!d[tmp]) q[++tt] = tmp;//若入度为0了,说明拓扑序轮到它了,入队
}
}
if(tt == n-1) return true;//如果所有点都入队了,说明是无环图
else return false;
}
int main()
{
memset(h,-1,sizeof h);//一定要初始化每个头结点为空
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>x>>y;
add(x,y);
}
if(TopSort())
{
for(int i=0;i<n;i++)//出队时只是头指针移动,实际数组元素不变,队列序就是拓扑序
{
cout<<q[i]<<" ";
}
puts("");
}
else puts("-1");
return 0;
}
六、最短路算法
源点:起点
汇点:终点
单源最短路:只有一个起点
多源汇最短路:多个起点
1.朴素版dijkstra算法
第一步:
初始化距离 dist[1] = 0,dist[i] = 正无穷
第二步:
for i :[1,n)
t <---找出不在集合S中的且距离最近的点
s<---t 把t加到s里去
用t更新其他点的距离
Dijkstra求最短路 I
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505;
const int INF = 0x3f3f3f3f;//最大int值,无穷远
int g[N][N]; //邻接矩阵存储稠密图
int dist[N]; //1号点到n号点的距离
int n,m,x,y,z;
bool st[N];//是否确定了最短距离
int Dijkstra()
{
dist[1] = 0;//起点距离为0
for(int i=1;i<=n;i++)//n个点,循环n次
{
int t = -1;//存储当前未确定最短距离,且距离最小的点
for(int j = 1;j <= n;j++)//o(n)寻找距离最小的点
{
if(!st[j] && (t==-1 || dist[j] < dist[t]))
{
t = j;
}
}
st[t] = true;//标记已确定最短距离
for(int j=1;j<=n;j++)//用该点更新所有点的最短距离
{
dist[j] = min(dist[j],dist[t] + g[t][j]);
}
}
if(dist[n] == INF) return -1;//无穷远说明没有通路
else return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,INF,sizeof g);//初始化图中所有路为无穷远
memset(dist,INF,sizeof dist);//初始化所有距离为无穷远
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
g[x][y] = min(g[x][y],z);//图中存在重边和自环,取最短的路即可解决
}
cout<<Dijkstra()<<endl;
}
ACM中常用的无穷大常量——0x3f3f3f3f
2.堆优化版的dijkstra算法
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int M = 1.5*1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int,int>PII;//first表示与起点距离,second表示几号点
int h[M],e[M],ne[M],w[M],idx;//稀疏图使用邻接表来存储,w[]表示边的权重
int n,m,x,y,z;
int dist[M];
bool st[M];
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;//维护边的权重
ne[idx] = h[a];
h[a] = idx;
idx++;
}
int Dijkstra()//堆优化版Dijkstra算法,有点像bfs
{
priority_queue<PII,vector<PII>,greater<PII>>heap;//定义一个小根堆
heap.push({0,1});//首先1号点距离为0,入堆
dist[1] = 0;//同上
while(!heap.empty())
{
PII t = heap.top();
heap.pop();
int num = t.second,distance = t.first;
if(st[num]) continue;//如果已确定最短距离,跳过
st[num] = true;//注意:维护已确定最短距离的状态的顺序
for(int i = h[num];i!=-1;i=ne[i])//用已确定的距离最短的点来维护相邻点的最短距离
{
int num1 = e[i];
if(dist[num1] > distance + w[i])
{
dist[num1] = distance + w[i];
heap.push({dist[num1],num1});
}
}
}
if(dist[n] == INF) return -1;//没有通路
else return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
memset(dist,INF,sizeof dist);//初始化链表为空,最短距离为无穷远
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
add(x,y,z);
}
cout<<Dijkstra()<<endl;
return 0;
}
3.Bellman-Ford算法(处理负边权)
有解的图中可能不存在负权回路,该算法能找出负权回路
有边数限制的最短路
backup数组的意义:防止一次更新多个点,发生串联现象,这样就无法保证是经过k条边的限制,因此只能使用上次迭代的结果,将其备份为backup
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 505,M = 10005,INF = 0x3f3f3f3f;
int n,m,k,x,y,z;
int dist[N],backup[N];//backup用于记录上一次的dist,防止在一次i循环内更新多个点的距离
struct edge
{
int x;
int y;
int z;
}Edge[M];
int Bellman_Ford()
{
memset(dist,INF,sizeof dist);//首先更新每个点的dist为无穷远
dist[1] = 0;//源点距离为0
for(int i=0;i<k;i++)//题目要求最多走k条边
{
memcpy(backup,dist,sizeof dist);//将上一次i循环后的dist复制给backup
for(int j=0;j<m;j++)
{
int a = Edge[j].x;
int b = Edge[j].y;
int w = Edge[j].z;
dist[b] = min(dist[b],backup[a] + w);
}
}
if(dist[n] >= INF/2) return -2;//由于有负边,当不满足题意时,终点距离不一定为无穷远
//但一定为一个大数
else return dist[n];
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
Edge[i] = {x,y,z};
}
int t = Bellman_Ford();
if(t == -2) puts("impossible");
else cout<<t<<endl;
return 0;
}
4.SPFA算法(用的比较多)
本题也可以用堆
注意:本题中的st数组表示是否在队列中,出队和入队都需要维护
spfa求最短路
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;
int h[M],e[M],w[M],ne[M],idx;
int dist[M];
bool st[M];//表示该点是否处于队列中,1为是,0为否
int n,m,x,y,z;
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
int spfa()//思想类似dijkstra算法,不同之处在于st的含义
{
memset(dist,INF,sizeof dist);
dist[1] = 0;
queue<int>q;
q.push(1);
st[1] = true;
while(!q.empty())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];//若发生距离更新
if(!st[j])//且该点不在队列中
{
q.push(j);//入队并更新st[j]的状态
st[j] = true;
}
}
}
}
if(dist[n] == INF) return -2;//返回-1会wa掉,可能有负边
else return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
add(x,y,z);
}
int t = spfa();
if(t==-2) puts("impossible");
else cout<<t<<endl;
return 0;
}
spfa判断负环
注意:起初时一定要把所有点加入队列,因为1号点不一定能到达n号点
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;
int h[M],e[M],w[M],ne[M],idx;
int dist[M],cnt[M];//记录从1号点到n号点路径中边的个数
bool st[M];//表示该点是否处于队列中,1为是,0为否
int n,m,x,y,z;
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
bool spfa()//spfa算法求是否存在负环,思路来源于抽屉法
{
memset(dist,INF,sizeof dist);
//dist[1] = 0;
queue<int>q;
for(int i=1;i<=n;i++)//因为从1号点可能到不了n号点,因此起初必须把每个点都入队
{
q.push(i);
st[i] = true;
}
while(!q.empty())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];//若发生距离更新,且该点不在队列中
cnt[j] = cnt[t] + 1;//相邻点的个数相差1
if(cnt[j] >= n) return true;//若该路径上的边个数大于等于所有点的个数,则一定存在负环
if(!st[j])
{
q.push(j);//入队并更新st[j]的状态
st[j] = true;
}
}
}
}
return false;//没有负环
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
add(x,y,z);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
Floyd算法(O^3)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 205,INF = 0x3f3f3f3f;
int n,m,x,y,z,k;
int d[N][N];
void Floyd() //动态规划
{
for(int k=1;k<=n;k++) //k必须在外循环,i,j循环次序可变
{
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;//相同点距离为0
else d[i][j] = INF;//否则设为无穷远
}
}
for(int i=1;i<=m;i++)
{
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");//存在负边,所以不通的路径距离不一定为无穷大
else cout<<d[x][y]<<endl;
}
return 0;
}
七、最小生成树
朴素版Prim算法(O^2)
858. Prim算法求最小生成树
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];//表示点到集合中某点的距离
bool st[N];
int prim()
{
int res = 0;
memset(dist,INF,sizeof dist);
for(int i=0;i<n;i++)
{
int t = -1;
for(int j=1;j<=n;j++)
{
if(!st[j] && (t == -1 || dist[j] < dist[t])) //j在集合外,且未找到任何一个点或j距离较小
{
t = j;
}
}
if(i && dist[t] == INF) return INF; //不是第一个点且当前距离最近的点的距离都是无穷,
//说明图不连通
if(i) res += dist[t];//只要不是第一条边,就把t的距离加入到答案去
for(int j=1;j<=n;j++)
{
dist[j] = min(dist[j],g[t][j]);
}
st[t] = true; //标志着当前点加入到集合中
}
return res;
}
int main()
{
cin>>n>>m;
memset(g,INF,sizeof g);
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b] = g[b][a] = min(c,g[a][b]);//无向图
}
int t = prim();
if(t == INF) puts("impossible");
else cout<<t<<endl;
return 0;
}
2022年3月20日AC版简化写法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int g[N][N];
bool st[N];
int dist[N];
int n,m,a,b,c;
int sum;
int prim()
{
memset(dist,INF,sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )
{
int Minj = 0;
for (int j = 1; j <= n; j ++ )
{
if(!st[j] && dist[j] < dist[Minj])
{
Minj = j;
}
}
if(dist[Minj] == INF) return INF;
sum += dist[Minj];
st[Minj] = true;
for (int j = 1; j <= n; j ++ )
{
dist[j] = min(dist[j],g[Minj][j]);
}
}
return sum;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
if(i==j)
{
g[i][j] = 0;
}
else g[i][j] = INF;
}
}
while (m -- )
{
scanf("%d%d%d", &a, &b,&c);
g[a][b] = min(g[a][b],c);
g[b][a] = min(g[b][a],c);
}
int t = prim();
if(t >= INF/2) puts("impossible");
else cout<<t<<endl;
return 0;
}
Kruskal算法(快排+并查集)
859. Kruskal算法求最小生成树
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 2e5+20,INF = 0x3f3f3f3f;
int p[M];
int n,m,a,b,c;
struct Edge
{
int a;
int b;
int w;
}edges[M];
int find(int a) //查找a点的集合的祖宗
{
if(a!=p[a]) p[a] = find(p[a]);
return p[a];
}
bool cmp(struct Edge a,struct Edge b)
{
return a.w < b.w;
}
void kruskal()
{
int cnt = 0,res = 0;
sort(edges+1,edges+m+1,cmp);//对每个边进行升序排序
for(int i=1;i<=n;i++) //初始化所有集合
{
p[i] = i;
}
for(int i=1;i<=m;i++)
{
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
a = find(a),b = find(b); //若不在一个集合,则合并
if(a!=b)
{
p[b] = a;
cnt++;
res += w;
}
}
if(cnt < n-1) puts("impossible");//合并次数小于n-1,说明图不连通
else cout<<res<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
edges[i] = {a,b,c};
}
kruskal();
return 0;
}
染色法判定二分图
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
二分图当且仅当图中不含奇数环
染色法判定二分图
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10,M = 2*N;
int h[N], e[M], ne[M], idx;
int n,m,a,b;
int color[N];
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u,int c) //返回的是能否顺利的无矛盾的进行染色
{
color[u] = c;
for(int i=h[u];i!=-1;i=ne[i])
{
int j = e[i];
if(!color[j]) //如果没有被染色,就把它所在的连通块进行染色
{
if(!dfs(j,3-c)) return false;
}
else if(color[j] == c) return false;//如果被染色了,就进行检查,颜色一致视为矛盾
}
return true;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
scanf("%d%d", &a, &b);
add(a,b);
add(b,a);
}
bool f = true; //是否有矛盾发生
for(int i=1;i<=n;i++)
{
if(!color[i])
{
if(!dfs(i,1))
{
f = false;
break;
}
}
}
if(f) puts("Yes");
else puts("No");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)