搜索与图论
1. DFS
DFS排列数字
给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
#include<iostream>
using namespace std;
const int N=10;
int path[N];
bool sign[N]; //看这个位置有没有搜索过
void dfs(int u,int n){
if(u==n){ //如果u=n说明DFS已经遍历到了第n层,输出
for(int i=0; i<n; i++) cout<<path[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++){ //填1-n之间的数
if(sign[i]==false){ //如果i没有被用过
path[u]=i;
sign[i]=true;
dfs(u+1,n); //递归到下一层。
sign[i]=false;//递归完之后要回头搜索,而path里面的值会被覆盖,不需要恢复。
}
}
}
int main(){
int n;
cin>>n;
dfs(0,n);
return 0;
}
n-皇后问题
n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gbaTNhLP-1671943637107)(./assets/19_860e00c489-1_597ec77c49-8-queens.png)]
现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n。
输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。
其中
.
表示某一个位置的方格状态为空,Q
表示某一个位置的方格上摆着皇后。每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1 ≤ n ≤ 9 1≤n≤9 1≤n≤9
#include<iostream>
using namespace std;
const int N=10;
char g[N][N];
bool col[N],dg[2*N],udg[2*N]; //列,对角线和反对角线,对角线比列数多,直接开两倍
void dfs(int u,int n){
if(u==n){//当放完最后一个皇后
for(int i=0; i<n; i++) cout<<g[i]<<endl;
cout<<endl;
return;
}
for(int i=0;i<n;i++){
// if(!col[i] && !dg[u+i]&& !udg[n-u+i]){
if(col[i]==0 && dg[u+i]==false && udg[n-u+i]==false){
//u+i 和 n-u+i 分别是第u行第i列两个对角线的标号。
g[u][i]='Q';
col[i]=dg[u+i]=udg[n-u+i]=true;
dfs(u+1,n);
col[i]=dg[u+i]=udg[n-u+i]=false;
g[u][i]='.';
}
}
}
int main(){
int n;
cin>>n;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
g[i][j]='.';
dfs(0,n);
return 0;
}
2. BFS
走迷宫
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。
数据保证 (1,1)处和 (n,m)处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N=101;
int n,m;
int g[N][N];//存图
int d[N][N];//存当前点到起点的距离,初始化为-1表示没有访问过这个点
PII q[N*N];
PII Prev[N][N];
int bfs(){
//初始化队列
int hh=0,tt=0;
q[tt++]={0,0};
memset(d,-1,sizeof d);
d[0][0]=0;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //分别表示向左,向下,向右,向上
while(hh<tt){
auto t=q[hh++]; //队头元素出队
for(int i=0; i<4; i++){ //向4个方向扩展
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0 && x<n &&y>=0 && y<m && g[x][y]==0 && d[x][y] == -1){
d[x][y]= d[t.first][t.second] + 1;
Prev[x][y]=t; //(x,y)这个点的前一个点t
q[tt++]= {x,y}; //新扩展出来的点入队
}
}
}
//输出路径
// int x=n-1,y=m-1;
// while(x || y){
// cout<<x<<" "<<y<<endl;
// auto t = Prev[x][y];
// x=t.first,y=t.second;
// }
return d[n-1][m-1];
}
int main(){
cin>>n>>m;
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
cin>>g[i][j];
cout<<bfs()<<endl;
}
3. 树
树的存储
树与图的存储
树是一种特殊的图,与图的存储方式相同。
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g[a][b] 存储边a->b
(2) 邻接表:
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;
// 添加一条边a->b
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
// 初始化
idx = 0;
memset(h, -1, sizeof h);
作者:yxc
链接:https://www.acwing.com/blog/content/405/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
树的遍历
树与图的遍历
时间复杂度 O(n+m), n 表示点数,m 表示边数
(1) 深度优先遍历 —— 模板题 AcWing 846. 树的重心
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
(2) 宽度优先遍历 —— 模板题 AcWing 847. 图中点的层次
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
树的重心
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b之间存在一条边。
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围
1 ≤ n ≤ 105 1≤n≤105 1≤n≤105
#include<iostream>
#include<cstring>
using namespace std;
const int N=100001,M=N*2;
// M=N*2 因为两个节点间无向图要存两条边,而题目说有n-1条边
int n,m;
int h[N],e[M],ne[M],idx;
bool visit[N];
int ans=N; //剩余各个连通块中点数的最大值,初始化为N
void add(int k, int x){
e[idx]=x;
ne[idx]=h[k];
h[k]=idx;
idx++;
}
//以u为根的子树中点的数量
int dfs(int u){
visit[u]= true; //标记为已访问
int sum=1,res=0;
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(visit[j]==false){
int s=dfs(j);
res=max(res, s); //res是删除的这个点的所有子树中最多点的那个
sum+=s; //需要把所有子树的点的数量加起来算n-sum
}
}
res=max(res ,n-sum); //删除的这个点的子树的点的数量res 和 剩下的n-sum
ans= min(ans, res); //在所有的结果中求最小值
return sum;
}
int main(){
cin>>n;
memset(h, -1, sizeof h);
for(int i=0; i<n-1; i++){
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
dfs(1);
cout<<ans<<endl;
return 0;
}
图中点的层次
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。
所有边的长度都是 1,点的编号为 1∼n。
请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1 的边。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
数据范围
1≤n,m≤105
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx;
int d[N],q[N];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
int bfs(){
int hh=0,tt=0;
q[0]=1;
memset(d, -1, sizeof d);
d[1]=0;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t]; i!= -1; i=ne[i]){
int j=e[i];
if(d[j]== -1){
d[j]=d[t]+1;
q[++tt]=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();
return 0;
}
4. 有向图的拓扑排序
给定一个 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 ≤ 1 0 5 1≤n,m≤10^5 1≤n,m≤105
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100001;
int h[N],e[N],ne[N],idx;
int q[N],d[N]; //q为存拓扑序的队列,d为每个节点的入度
void add(int a, int b){ //添加一条a到b 的边
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
bool topsort(int n){ //返回值代表是否有拓扑序
int hh=0, tt=-1;
for(int i=1; i<= n; i++){
if(!d[i]) q[++tt]=i; //如果入度为0,就让它入队
}
while(hh <= tt)
{
int t = q[hh++];
//取出队头,也就是最先进队的,把它后面连的点的入度减一,然后看是否为0
//为0 则入队
for(int i=h[t]; i!= -1; i=ne[i]){
int j=e[i];
d[j]--;
if(d[j]==0) q[++tt]=j;
}
}
return tt==n-1;
}
int main(){
int n,m;
cin>>n>>m;
memset(h, -1, sizeof h);
for(int i=0; i<m; i++){
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;
}
if(topsort(n)){
for(int i=0; i<n; i++) cout<<q[i]<<" ";
cout<<endl;
}
else cout<<-1;
return 0;
}
5.dijkstra算法
朴素Dijkstra求最短路 I
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤10^5,
图中涉及边长均不超过10000。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=501;
int n,m;
int g[N][N]; //图的邻接矩阵
int dist[N]; //当前点到起点的距离
bool st[N];
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0; //起点
for(int i=0; i<n; i++){ //迭代n次,就可以求出最短路
int t=-1; //
for(int j=1; j<=n; j++){
if(!st[j] && (t==-1 || dist[t]>dist[j]))
t=j;
}
st[t]=true; //把t加入S集中
for(int j=1; j<=n; j++){ //看加入t之后能不能代替某些路径
dist[j]=min(dist[j], dist[t]+g[t][j]);
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
cin>>n>>m;
memset(g, 0x3f, sizeof g);
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c); //有重边,取较短的那一条
}
int t=dijkstra();
cout<<t<<endl;
return 0;
}
堆优化dijkstra
//堆优化dijkstra
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储
int dist[N]; //当前点到起点的距离
bool st[N];
void add(int a, int b, int c){
e[idx]=b;
w[idx]=c; //边长
ne[idx]=h[a];
h[a]=idx;
idx++;
}
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0; //起点
priority_queue<PII,vector<PII>, greater<PII>> heap;
heap.push({0, 1}); //起点进堆
while(heap.size())
{
auto t=heap.top();
heap.pop(); //取出堆头元素
int ver= t.second, distance=t.first;
if(st[ver]) continue; //如果这个点在S集里面,略过
st[ver]=true;
for(int i=h[ver]; i!=-1; i=ne[i]){
int j=e[i];
if(dist[j]>distance + w[i]){
dist[j]=distance + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); //使用邻接表存储图
}
int t=dijkstra();
cout<<t<<endl;
return 0;
}
6. Bellman_ford算法
有边数限制的最短路
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 1 号点到 n 号点的最多经过 kk 条边的最短距离,如果无法从 1 号点走到 n 号点,输出
impossible
。注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
点的编号为 1∼n。
输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。
如果不存在满足条件的路径,则输出
impossible
。数据范围
1≤n,k≤500,
1≤m≤10000,
1≤x,y≤n,
任意边长的绝对值不超过 10000。
#include<iostream>
#include<cstring>
using namespace std;
const int N=510,M=10001;
int n,m,k;
int dist[N],backup[N];
struct Edge{
int a,b,w;
}edges[M];
int bellman_ford(){
//初始化
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
for(int i=0; i<k; i++){ //不超过k条边,使用k次迭代
memcpy(backup, dist, sizeof dist); //每次用前一次的结果,防止串联导致不满足不超过k条边
for(int j=0; j<m; j++){
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dist[b]=min(dist[b], backup[a]+w);
}
}
//if(dist[n]>0x3f3f3f3f/2) return -1;
return dist[n];
}
int main(){
cin>>n>>m>>k;
for(int i=0; i<m; i++){
int a,b,w;
cin>>a>>b>>w;
edges[i]={a,b,w};
}
int t=bellman_ford();
if(t>0x3f3f3f3f/2) cout<<"impossible"<<endl;
//除以2是因为有负权边,正无穷的值可能被迭代不等于0x3f
else cout<<t<<endl;
return 0;
}
7.spfa算法
spfa求最短路,
比dijkstra快,但某些情况会被卡
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出
impossible
。数据保证不存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出
impossible
。数据范围
1≤n,m≤1051≤n,m≤105,
图中涉及边长绝对值均不超过 1000010000。
//SPFA,不被卡的话比dijkstra快,阴险的出题人会卡SPFA
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储
int dist[N]; //当前点到起点的距离
bool st[N];
void add(int a, int b, int c){
e[idx]=b;
w[idx]=c; //边长
ne[idx]=h[a];
h[a]=idx;
idx++;
}
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;
while(q.size()>0){
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]==false){
q.push(j); //让更新后变小了之后的点入队,如果没变小就不管
st[j]=true;
}
}
}
}
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); //使用邻接表存储图
}
int t=spfa();
if(t== 0x3f3f3f3f) cout<<"impossible"<<endl;
else cout<<t<<endl;
return 0;
}
spfa判断负环
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
如果图中存在负权回路,则输出
Yes
,否则输出No
。数据范围
1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。
//SPFA,不被卡的话比dijkstra快,阴险的出题人会卡SPFA
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储
int dist[N],cnt[N]; //当前点到起点的距离
bool st[N];
void add(int a, int b, int c){
e[idx]=b;
w[idx]=c; //边长
ne[idx]=h[a];
h[a]=idx;
idx++;
}
int spfa(){
//不需要初始化,因为求得不是距离,是负环
// memset(dist, 0x3f, sizeof dist);
// dist[1]=0;
queue<int> q;
for(int i=1; i<=n; i++){
st[i]=true;
q.push(i);
}
//因为如果从1开始的话,可能到不了负环,
//因此要把所有的点都加入队列
// q.push(1);
// st[1]=true;
while(q.size()>0){
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;
if(cnt[j] >=n) return true;
if(st[j]==false){
q.push(j); //让更新后变小了之后的点入队,如果没变小就不管
st[j]=true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); //使用邻接表存储图
}
if(spfa()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
8. floyd算法
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。
#include<iostream>
#include<cstring>
using namespace std;
const int N=210, INF=1e9;
int n,m,q;
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>>q;
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--){
int a,b,w;
cin>>a>>b>>w;
d[a][b]=min(d[a][b], w); //可能有重边
}
floyd();
while(q--){
int a,b;
cin>>a>>b;
if(d[a][b]>INF/2) cout<<"impossible"<<endl;
else cout<<d[a][b]<<endl;
}
return 0;
}
9. prim算法(求最小生成树)
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出
impossible
。给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,EE 表示图中边的集合,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≤10^5,
图中涉及边的边权的绝对值均不超过 10000。
#include<iostream>
#include<cstring>
using namespace std;
const int N=501,INF=0x3f3f3f3f;
int n,m; //点数和边数
int g[N][N]; //稠密图用邻接矩阵
int dist[N];
bool st[N]; //更新过点之后置为true,默认为FALSE
int prim(){
memset(dist, 0x3f, sizeof dist);
int res=0;
for(int i=0; i<n; i++){ //迭代n次
int t=-1;
for(int j=1; j<=n; j++){
if(st[j]==false && (t==-1 || dist[t] > dist[j]))
t=j;
}
if(i && dist[t]==INF) return INF; //如果不是第一次更新且dist[t]为正无穷则不存在
for(int j=1; j<=n; j++) dist[j]=min(dist[j], g[t][j]);
if(i) res+=dist[t];//会出现自环的问题,在main函数里面处理了一下
st[t]=true;
}
return res;
}
int main(){
cin>>n>>m;
memset(g, 0x3f, sizeof g);
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b], c); //有重边取较短边
}
for(int i=1; i<=n; i++){
g[i][i]=INF; //去掉自环
}
int t=prim();
if(t==INF) cout<<"impossible"<<endl;
else cout<<t<<endl;
return 0;
}
10. Kruskal算法(求最小生成树)
给定一个 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 ≤ 1 0 5 1≤n≤10^5 1≤n≤105,
1 ≤ m ≤ 2 ∗ 1 0 5 1≤m≤2*10^5 1≤m≤2∗105,
图中涉及边的边权的绝对值均不超过 1000。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200001;
int n,m;
int p[N];
struct Edge{
int a,b,w;
bool operator< (const Edge &W)const
{
return w<W.w;
}
}edges[N];
//并查集
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main(){
cin>>n>>m;
for(int i=0; i<m; i++){
int a,b,w;
cin>>a>>b>>w;
edges[i]={a,b,w};
}
sort(edges,edges+m);
for(int i=1; i<=n; i++) p[i]=i; //初始化
int res=0,cnt=0;
for(int i=0; i<m; i++){
int a=edges[i].a, b=edges[i].b, w=edges[i].w;
a=find(a),b=find(b);
if(a!=b){//a和b不连通
p[a]=b;// 让a和b联通
res+=w;
cnt++;
}
}
if(cnt < n-1) cout<<"impossible";
else cout<<res;
return 0;
}
11. 二分图
染色法判定二分图
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
输入格式
第一行包含两个整数 n 和 m。
接下来 mm 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。
输出格式
如果给定图是二分图,则输出
Yes
,否则输出No
。数据范围
1≤n,m≤105
#include<iostream>
#include<cstring>
using namespace std;
const int N=100001,M=2*N;
int h[N],e[M],ne[M],idx;
int color[N];
int n,m;
void add(int a, int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
bool dfs(int u,int c){
color[u]=c; //把u这个点染成c
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(!color[j]){
//把j这个点染成3-c(1或2)
if(!dfs(j,3-c)) return false; //如果返回值为false,说明有矛盾
}
else if(color[j]==c) return false; //如果j已经染色并且和u相同,那么就有矛盾
}
return true; //没有矛盾
}
int main(){
cin>>n>>m;
memset(h, -1, sizeof h); //初始化h[N]
while(m--){
int a,b;
cin>>a>>b;
add(a,b), add(b,a); //无向图
}
bool flag=true; //染色过程是否出现矛盾
for(int i=1; i<=n; i++){
if(!color[i]){ //如果i没染色
if(!dfs(i,1)){
flag=false;
break;
}
}
}
if(flag) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
匈牙利算法解决二分图最大匹配
给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
输入格式
第一行包含三个整数 n1、 n2 和 m。
接下来 m 行,每行包含两个整数 u和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。
输出格式
输出一个整数,表示二分图的最大匹配数。
数据范围
1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105
#include<iostream>
#include<cstring>
using namespace std;
const int N=501, M=100001;
int n1,n2,m; //分别表示左半边、右半边点的数量,以及之间的边数
int h[N],e[M],ne[M],idx; //邻接表存储
int match[N]; //存匹配的边的情况
bool st[N];
//图的邻接表存储
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
//匈牙利算法——二分图匹配
bool find(int x){
for(int i=h[x]; i!=-1; i=ne[i]){
int j=e[i];
if(!st[j]){ //如果女生j还没有喜欢其它男生
st[j]=true;
if(match[j]==0 || find(match[j])){
//如果女生j没和其它男生配对,或者x可以找到除j外的第二个
match[j]=x; //就让x跟j匹配
return true;
}
}
}
return false;
}
int main(){
cin>>n1>>n2>>m;
memset(h, -1, sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b); //在a、b之间加一条边,表示男生a正在追女生b
}
int res=0;
for(int i=1; i<=n1; i++){
memset(st, false, sizeof st);
if(find(i)) res++;
}
cout<<res;
return 0;
}
n2个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
输入格式
第一行包含三个整数 n1、 n2 和 m。
接下来 m 行,每行包含两个整数 u和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。
输出格式
输出一个整数,表示二分图的最大匹配数。
数据范围
1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105
#include<iostream>
#include<cstring>
using namespace std;
const int N=501, M=100001;
int n1,n2,m; //分别表示左半边、右半边点的数量,以及之间的边数
int h[N],e[M],ne[M],idx; //邻接表存储
int match[N]; //存匹配的边的情况
bool st[N];
//图的邻接表存储
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
//匈牙利算法——二分图匹配
bool find(int x){
for(int i=h[x]; i!=-1; i=ne[i]){
int j=e[i];
if(!st[j]){ //如果女生j还没有喜欢其它男生
st[j]=true;
if(match[j]==0 || find(match[j])){
//如果女生j没和其它男生配对,或者x可以找到除j外的第二个
match[j]=x; //就让x跟j匹配
return true;
}
}
}
return false;
}
int main(){
cin>>n1>>n2>>m;
memset(h, -1, sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b); //在a、b之间加一条边,表示男生a正在追女生b
}
int res=0;
for(int i=1; i<=n1; i++){
memset(st, false, sizeof st);
if(find(i)) res++;
}
cout<<res;
return 0;
}