DFS
千万注意DFS递归调用时要恢复现场!!!!!
例题
#include<iostream>
using namespace std;
const int N = 10;
int n;
int path[N];
bool ifvisit[N];
void DFS(int a) //a代表现在填完第几个数字了
{
if(a==n)
{
for(int i=1;i<=n;i++) printf("%d ",path[i]);
puts("");
return;
}
else{
for(int i=1;i<=n;i++){
if(!ifvisit[i]){
path[a+1] = i;
ifvisit[i] = true;
DFS(a+1);
ifvisit[i] = false;
}
}
}
}
int main()
{
cin >> n;
DFS(0);
return 0;
}
第一种思想
#include<iostream>
using namespace std;
const int N = 20;
int n;
char graph[N][N];
bool col[N],diagonal[N],rdiagonal[N]; //记录第几列,第几个正对角线,第几个斜对角线是否有了皇后
//正斜对角线的坐标想一个坐标系一个y = x+b 和 y = -x+b 的截距,又因为截距不能是负数,所以加上一个偏移量就好了
//正对角线的坐标b=y-x +n(加上个偏移量,反正我只需要每个位置有个对应的就好了) 斜对角线b = x+y
void DFS(int x) //x代表了已经完成了第几行
{
if(x==n){
for(int i=0;i<n;i++) puts(graph[i]);
puts("");
return;
}
else{
for(int i=0;i<n;i++){
if(!col[i]&&!diagonal[x-i+n]&&!rdiagonal[i+x]){
col[i] = diagonal[x-i+n] = rdiagonal[i+x] = true;
graph[i][x] = 'Q';
DFS(x+1);
col[i] = diagonal[x-i+n] = rdiagonal[i+x] = false;
graph[i][x] = '.';
}
}
}
}
int main()
{
cin >> n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
graph[i][j] = '.';
}
}
DFS(0);
return 0;
}
第二种思想,按照格子进行搜索
#include<iostream>
using namespace std;
const int N = 10;
int n;
char graph[N][N];
bool row[N],col[N],diagonal[2*N],rdiagonal[2*N];
//记录第几列,第几个正对角线,第几个斜对角线是否有了皇后
//正斜对角线的坐标想一个坐标系一个y = x+b 和 y = -x+b 的截距,又因为截距不能是负数,所以加上一个偏移量就好了
//正对角线的坐标b=y-x +n(加上个偏移量,反正我只需要每个位置有个对应的就好了) 斜对角线b = x+y
void DFS(int x,int y,int num) //x代表横坐标,y代表纵坐标,num代表已经放置的个数
{
if(num>n) return;
if(y==n){
y=0;
x++;
}
if(x==n){
if(num==n){
for(int i=0;i<n;i++) puts(graph[i]);
puts("");
}
return;
}
graph[x][y] = '.';
//这个格子不放皇后时
DFS(x,y+1,num);
//这个格子放皇后时
if (!row[x] && !col[y] && !diagonal[x + y] && !rdiagonal[x - y + n])
{
row[x] = col[y] = diagonal[x + y] = rdiagonal[x - y + n] = true;
graph[x][y] = 'Q';
DFS(x, y + 1, num + 1);
graph[x][y] = '.';
row[x] = col[y] = diagonal[x + y] = rdiagonal[x - y + n] = false;
}
}
int main()
{
cin >> n;
DFS(0,0,0);
return 0;
}
BFS
基本框架
queue<node> q;
q.push(start_node); //初始状态入队
while(!q.empty())
{
node the_next = q.pop(); //获得队头
再把队头的子节点入队;
}
走迷宫
#include<iostream>
#include<queue>
using namespace std;
const int N = 110;
int n,m;
int graph[N][N]; //记录这个图
int record[N][N]; //记录当前点距离原点的距离
bool if_visit[N][N]; //记录那个点是否被访问过
struct Node{
int x,y;
int distance;
};
int x[4] = {-1,1,0,0};
int y[4] = {0,0,1,-1};
void BFS(int the_x,int the_y,int start_distance,int goal_x,int goal_y)
{
queue<Node> node;
Node start_node = {the_x,the_y,start_distance};
node.push(start_node);
while(!node.empty())
{
Node now_node = node.front();
if(now_node.x==goal_x&&now_node.y==goal_y) break;
node.pop();
int now_distance = now_node.distance;
int next_x,next_y,next_distance;
for(int i=0;i<4;i++){
next_x = now_node.x+x[i];
next_y = now_node.y+y[i];
if(next_x>=0 && next_x<n && next_y>=0 && next_y<m && !if_visit[next_x][next_y] && !graph[next_x][next_y])
{
if_visit[next_x][next_y] = true;
Node next_node = {next_x,next_y,now_distance+1};
node.push(next_node);
record[next_x][next_y] = next_node.distance;
}
}
}
}
int main(){
cin>> n >> m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>graph[i][j];
}
}
BFS(0,0,0,n-1,m-1);
cout<<record[n-1][m-1];
return 0;
}
八数码
图的存储邻接表实现
树是一种特殊的图
而无向图可以看做特殊的有向图
有向图需要建立a -> b的一条边, 那么无向图再把这个值复制到 b -> a里就好了
图的每个节点有个编号i , 空节点使用-1表示
i号点的值是 e[i] , i 号点指向的下一个位置就是 ne[i]
e[i] 存储i号节点的值
ne[i] 存储i号节点下一个节点的index
head[i] 存储有i号节点,包括i号节点和i号节点子节点的那条链的初始位置
head是按照节点名字存储的,比如1号节点就在head[1]
但是那个e数组不是按节点名字存储的
idx表示e中存储的节点数目,也是可以存储节点的数组中位置的开始,也就是当前用到的地址
int head[N],e[N],ne[N],idx;
//如果是无向图的话需要 e[N*2] , ne[N*2],因为到时候需要add(a,b),add(b,a),所以数组要开双倍的
void add(int a,int b) //这个函数表示b号节点是a号节点的子节点
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
有向图的存储一般使用邻接矩阵和邻接表
而邻接矩阵的空间复杂度比较高,所以最常用的就是邻接表
而邻接表就是给每一个节点开一个单链表(存储的是这个点可走到的点)
例题:树的重心
问题分析思路:
就是把每个节点都遍历一遍,看看删除这个节点之后,所形成的几个连通块中的有最大节点数的连通块的值是多少,然后找到这个值最小的哪个节点,所以我们可以使用bfs来进行遍历,因为使用bfs我们可以轻松的统计出来子树的大小,从而得到我们想要的点个数的信息
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
//这里ans = N就是对的,但是 ans = n 就是错的要注意
int ans = N; //ans记录的是全局答案,记录min(max(各个子树的子树的最大连通节点数))
int n; //记录的是图中的节点数
//head数组记录每个头结点的位置,e数组记录每个节点存储的值
//ne数组记录每个节点指向下一个节点的位置,idx记录当前已经被分配的节点的个数(同时也是可以开始分配节点的位置)
int head[N],e[N*2],ne[N*2],idx;
//这里开了个N*2是因为这是个无向图,无向图相当于我存了个a->b我还要再弄个b->a所以开了两倍的数组
bool if_visit[N];
//把值为num的数插入到a号节点的头结点之后
//这个样子我的head[i]指向的虽然不是i号节点,但是一定这条链表包含着i号节点和i号节点子节点的链表
void add(int a,int num) //值为a的节点指向值为num的节点
{
e[idx] = num;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
//当前已经搜索到u号节点
//这个DFS返回以u号节点作为根节点的树有多少个节点
int DFS(int u)
{
if_visit[u] = true;
int sum = 1;//记录当前子树的节点数目
int size = 0; //存储的是删除这个节点后,每个连通块的最大节点数
for(int i=head[u];i!=-1;i=ne[i])
{
int j = e[i];
if(if_visit[j]) continue; //continue跳出本轮循环,开始下一轮循环
//break是直接跳出循
int s = DFS(j); //s表示当前子树有多少个节点
sum += s;
size = max(s,size);
}
size = max(size,n-sum);
ans = min(ans,size);
return sum;
}
int main()
{
cin >> n;
memset(head,-1,sizeof head);
//注意的是如果head[N] = {-1}这样子赋值后整个数组还是0所以只能用memset
for(int i=0;i<n-1;i++)
{
int a,b;
cin >> a >> b;
add(a,b); //这道题e[i]存的值也就是这个点的编号的
add(b,a);
}
DFS(1);
cout << ans;
return 0;
}
例题:图中点的层次
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 100010;
int n,m;
int head[N],e[N],ne[N],idx;
int dis[N];
int add(int a,int b)
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
void BFS()
{
memset(dis,-1,sizeof dis);
dis[1] = 0;
queue<int> the_queue;
the_queue.push(1); //表示从几号节点开始
while(!the_queue.empty())
{
int now_node = the_queue.front();
int now_dis = dis[now_node];
the_queue.pop();
for(int i=head[now_node];i!=-1;i=ne[i])
{
int j = e[i];
if(dis[j]==-1)//表示这个子节点一定不是重环
{
the_queue.push(j);
dis[j] = now_dis + 1;
}
}
}
}
int main()
{
memset(head,-1,sizeof head);
cin >> n >> m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
BFS();
cout <<dis[n];
return 0;
}
有向图的拓扑序列
拓扑序列是针对有向图来说的,无向图是没有拓扑序列的
可以证明,一个有向无环图一定存在一个拓扑序列
所以有向无环图又被称为拓扑图
例题
给定一个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<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010;
int head[N],e[N],ne[N],idx;
int e_degree[N]; //表示每个点的入度
int n,m;
int add(int a,int b)
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
int q[N];
bool topology()
{
int front = 0 , tail = -1;
//先找到第一个入度为0的点入队
for(int i=1;i<=n;i++)
{
if(e_degree[i]==0){
tail++;
q[tail] = i;
}
}
while(front<=tail)
{
int now_node = q[front];
front++;
for(int i=head[now_node];i!=-1;i=ne[i])
{
int j = e[i];
e_degree[j]--;
if(e_degree[j]==0){
tail++;
q[tail] = j;
}
}
}
return tail==n-1;//只有有向无环图才有拓扑序列,且有向无环图最终一定会把n个节点都加进去的
}
int main()
{
memset(head,-1,sizeof head);
cin >> n >> m;
while(m--)
{
int a,b;
cin >> a >> b;
add(a,b);
e_degree[b]++;
}
if(topology()){
for(int i=0;i<n;i++) cout<<q[i]<<" ";
}
else cout<<-1;
return 0;
}
最短路问题
n是节点数, m是边数
如果n和m是一个级别的话,是稀疏图
如果n2和m是一个级别的话,是稠密图
稠密图用邻接矩阵存储,稀疏图用邻接表存储
朴素Dijkstra
Dijkstra要求图中不能有负权边
步骤:
集合S存放当前已经确定最短路的点
求1号点到整个图所有点的距离
- dis[1] = 0 , dis[i] = +无穷(只有第一个点有距离,其余点都没有距离)
- for(i:从1~n)
t <- 不在s中的距离最近的点
s <- t
用点t来更新其他所有点的距离
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510;
int n,m;
int dis[N]; //记录从1号点到i号点的距离
bool st[N]; //记录i号点是否被加入了已经确定最短路的集合中
int g[N][N]; //邻接矩阵
//注意:这个题是有自环和重边的
//如果是自环的话,由于我们要求最短路径,所以在这个Dijstra算法中可以不管自环,因为算自环一定会把距离增加
//对于重边,我们读入最短的那个边
//memset会把每个字节都写成0x3f,dist[n]是int型变量,包含4个字节,所以一定要写成0x3f3f3f3f
void Dijstra()
{
memset(dis,0x3f,sizeof dis);
dis[1] = 0; //第一步,1号节点距离为0
for(int i=1;i<=n;i++)//开始循环,一次把一个点加入st中
{
int t = -1; //表示未被加入st中,距离最小的点,初始为-1是为了找到源节点
for(int j=1;j<=n;j++)
{
if(!st[j]&&(t==-1 || dis[j]<dis[t])) t = j;
}
for(int j=1;j<=n;j++)
{
dis[j] = min(dis[j],dis[t]+g[t][j]);
}
st[t] = true;
}
}
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);
}
Dijstra();
if(dis[n]==0x3f3f3f3f) cout<<-1<<endl;
else cout<<dis[n]<<endl;
return 0;
}
堆优化Dijkstra
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
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 ++ ;
}
struct Node{
int distance; //这个点到源节点的距离
int num; //这个点的编号
};
struct cmp{
bool operator() (Node node1, Node node2){
return node1.distance > node2.distance; //>是大的放后头, <是小的放后头
}
};
priority_queue<Node,vector<Node>,cmp> heap;
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
heap.push({0, 1});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.num, distance = t.distance;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + 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);
}
cout << dijkstra() << endl;
return 0;
}
Bellman-ford
解决带有负权边的图
for n 次:(n个节点)
这里外层循环如果迭代了k次表示的是,最多不经过超过k条边的最短路的距离
所以,如果迭代了n次后,还被更新了
说明存在一条路径,经过至少n个边,那么如果有n个边,则最多有n+1个点,
但是只有n个点,所以,说明至少两个点是重复的,也就是图中有回路,也就是说图中有负环
所以Bellman-ford算法可以用来找负环,但是时间复杂度比较高
但是一般找负环是SPFA来做的
for循环所有边,a,b,w:表示从a->b的边的权重是w
//(所以Bellman-ford算法存边写个结构体就好了,不用邻接表和邻接矩阵:
//struct{
// int a,b,w;
//}edge[m];
//)
更新:dist[b] = min(dist[b],dist[a]+w);
经过Bellman-ford算法之后,一定会有 dist[b]<= dist[a] + w;
如果能够求出最短边,那么这个图是没有负权回路的
也就是说,如果有负权回路,那么一定求不出最短路径
例题:有边数限制的最短路
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510, M = 10010;
int n,m,k;
struct Edge{
int a,b,w;
}edges[M];
int dis[N];
int dis_backup[N]; //存储外层循环每次的备份,使得我仅仅使用上一次循环结束后的距离更新这次循环
//防止我这次循环点更新的点把我这次循环后更新的点更新了,从而使用的不是上次循环获得的值
int Bellman_ford()
{
memset(dis,0x3f,sizeof dis);
dis[1] = 0;
for(int i=1;i<=k;i++)
{
memcpy(dis_backup,dis,sizeof dis);
for(int j=0;j<m;j++)
{
int a = edges[j].a,b = edges[j].b,w = edges[j].w;
dis[b] = min(dis[b],dis_backup[a]+w);
}
}
if(dis[n] > 0x3f3f3f3f / 2) return -1;
else return dis[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 = a;
edges[i].b = b;
edges[i].w = w;
}
int value = Bellman_ford();
if(value==-1) puts("impossible");
else cout<<value<<endl;
return 0;
}
SPFA算法
使用SPFA算法要求不含有负环,只要不含有负环就可以使用
正权图也可以的
SPFA是对Bellman-ford算法的一个优化
for n 次
for 所有边 a,b,w
dis[b] = min(dis[b],dis[a]+w);
由于这一步只有dis[a]变化了dis[b]才会变化,SPFA就是基于这种思想进行优化的
优化方法是使用一个队列
qeueu<- 起点
while 队列不空
t <- q.front();
q.pop();
更新一下t的所有出边
如果更新成功就把更新的边加入队列
(思想就是更新过谁,把谁加入队列)
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 100010;
int head[N],e[N],ne[N],w[N],idx;
int dis[N];
bool st[N]; //判断i号节点是否加入到了队列中
int n,m;
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
void SPFA()
{
memset(dis,0x3f,sizeof dis);
dis[1] = 0;
queue<int> q; //记录距离被更新过的点
q.push(1);
while(!q.empty()) //结束条件是,所有点距离都不会再更新了
{
int t = q.front();
q.pop();
st[t] = false; //出队了标记上
for(int i=head[t];i!=-1;i=ne[i])
{
int j = e[i];
if(dis[j]>dis[t]+w[i]){//这里是dis[t]注意别写成dis[i]了,i是链表中的下标!
dis[j] = dis[t]+w[i];
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
}
int main()
{
memset(head,-1,sizeof head);
cin >> n >> m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
SPFA();
if(dis[n]==0x3f3f3f3f) cout<<"impossible";
else cout<<dis[n];
return 0;
}
SPFA求负环
记录 dis[N] 记录当前各个点到1号点的距离
记录 count[N]数组,记录当前到i号点最短路的边的数量
如果count[i]>=n 表示到i号点至少走了n个边
但是,如果有n个边,那么至少有n+1个点所以一定有两个点是相同的,必定路径上存在环
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 2010;
const int M = 10010;
int n,m;
int head[N],e[N],ne[N],w[N],idx;
int dis[N];
int cnt[N];
bool st[N];
int add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
bool SPFA()
{
memset(dis,0x3f,sizeof dis);
dis[1] = 0;
//由于可能存在从1号点走不到那个负环,所以把所有点都放进去
queue<int> q;
for(int i=1;i<=n;i++)
{
q.push(i);
st[i] = true;
}
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i=head[t];i!=-1;i=ne[i])
{
int j = e[i];
if(dis[j]>dis[t]+w[i])
{
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j]){
st[j] = true;
q.push(j);
}
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cout.tie(NULL);
cin>>n>>m;
memset(head,-1,sizeof head);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(SPFA()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
Floyd算法
d[k,i,j] 表示从i出发,经过k个点到达j的最短距离
Floyd算法
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210;
const int inf = 1e9;
int n,m,k;
int dis[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++)
{
dis[i][j] = min(dis[i][j],dis[i][k]+dis[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) dis[i][j] = 0;
else dis[i][j] = inf;
}
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
dis[a][b] = min(c,dis[a][b]);
}
Floyd();
while(k--)
{
int x,y;
cin>>x>>y;
if(dis[x][y] > inf/2) cout<<"impossible"<<endl;
else cout<<dis[x][y]<<endl;
}
return 0;
}
最小生成树问题
稠密图用朴素Prim算法,
稀疏图用堆优化Prim算法或者Kruskal算法(一般是用Kruskal算法)
朴素Prim算法
首先初始化所有距离为正无穷
然后 n 次迭代:
t <-找到不在集合中距离最小的点(集合为在当前连通块中的所有点)
用t更新其他点到集合的距离
把t加到集合中去
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int m,n;
int g[N][N];
int dis[N];
bool st[N];
const int INF = 0x3f3f3f3f;
int prim()
{
memset(dis,0x3f,sizeof dis);
int res = 0;
dis[1] = 0;
for(int i=0;i<n;i++)
{
int tt = -1;
for(int j=1;j<=n;j++)
{
if(!st[j] && (tt==-1 || dis[j]<dis[tt])) tt = j;
}
st[tt] = true;
for(int j=1;j<=n;j++){
if(j!=tt){
dis[j] = min(dis[j],g[tt][j]); //防止负数自环把自己更新了
}
}
if(dis[tt] > INF/2) return INF;
res += dis[tt];
}
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] = min(g[a][b],c);
g[b][a] = min(g[b][a],c);
}
int tt = prim();
if(tt == INF) puts("impossible");
else cout << tt << endl;
return 0;
}
kruskal算法
- 将所有边按照权重从小到大排序(sort: O(mlogm) 算法的瓶颈在sort上)
- 枚举每条边,a -> b , 权重为 c, 如果当前a,b不连通,就把a->b这条边加入集合中(结合并查集来实现)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n,m;
struct Edge{
int a,b,w;
}edge[N*2];
bool cmp(Edge& e1,Edge& e2){
return e1.w < e2.w;
}
int p[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;
edge[i].a = a;
edge[i].b = b;
edge[i].w = w;
}
sort(edge,edge+m,cmp);
for(int i=1;i<=n;i++)
{
p[i] = i;
}
int res = 0,cnt = n; //分别表示当前最多节点数连起来的图的最小距离,和连通块数目
//krusal算法开始
for(int i=0;i<m;i++)
{
int a = edge[i].a,b = edge[i].b,w = edge[i].w;
if(find(a)!=find(b)){
p[find(a)] = find(b);
cnt--;
res += w;
}
}
if(cnt > 1) puts("impossible");
else{
cout << res << endl;
}
return 0;
}
二分图
染色法
染色法用来判断一个图是否是二分图
一个图是二分图当且仅当图中不含奇数环(奇数环指环中边数的数量是奇数)
for(int i=1;i<=n;i++){
if(i号节点没有被染色)
dfs(i,1); // 把 i 所在的连通块整个染一遍
//1代表颜色
}
#include<iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int head[N],e[N*2],ne[N*2],idx;
int n,m;
int color[N]; // 记录每个点被染成的颜色0代表未染色,1代表一种颜色,2代表另一种颜色
void add(int a,int b)
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
//dfs就是寻找当前节点所在连通块有没有染色矛盾
bool dfs(int u,int col)
{
if(color[u]==0) color[u] = col;
else if(color[u]!=col) return false;
for(int i=head[u];i!=-1;i=ne[i]){
int j = e[i];
if(color[j]==0){ //子节点没有染色的话给子节点上色
if(dfs(j,3-col)==false) return false;
}
else if(color[j]==col) return false;
}
return true;
}
//bfs版本
struct Node{
int index,col;
};
bool bfs(int u,int col) //表示将要把u号节点染色成为col
{
queue<Node> q;
if(color[u]==0){
color[u] = col;
q.push({u,color[u]});
}
else{
if(color[u]!=col) return false;
}
while(!q.empty())
{
Node node = q.front();
int num = node.index;
int current_color = node.col;
q.pop();
for(int i=head[num];i!=-1;i=ne[i])
{
int j=e[i];
if(color[j]==0){
color[j] = 3 - current_color;
q.push({j,color[j]});
}
else if(color[j]==current_color) return false;
}
}
return true;
}
int main()
{
memset(head,-1,sizeof head);
cin >> n >> m;
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]==0){//表示未染色,我们的染色都是在dfs中完成的(也可以用bfs遍历)
if(dfs(i,1)==false){//那我么随便选一种颜色给这个节点染上,然后根据规则把这个节点所在的连通块全染色
flag = false;
break;
};
}
}
*/
bool flag = true;
for(int i=1;i<=n;i++)
{
if(color[i]==0){
if(bfs(i,1)==false){
flag = false;
break;
}
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
匈牙利算法
时间复杂度 : O(nm)
求解的是二分图中个数最大的那个集合
大家以前在“一笔画”等讲中已初步接触.所谓二分图,就是顶点集合可以划分成两个部分,V=V1+V2,如V1有p个点,记为V1={v1,v2…,vp},V2有q个点,记为V2={vp+1,vp+2…,vp+q},而V1中任意一点,不会与V1中其他点联结,而只能与V2中某些点联结;V2也如此
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 510;
const int M = 1e5 + 10;
int head[N],e[M],ne[M],idx;
//把二分图匹配想成两个集合一个男生集合,一个女生集合,寻找最大的可以组成对象的对数
int n1,n2,m;
int match[N]; //表示i号女生的男朋友是谁
bool st[N]; //表示是否已经尝试过和这个女生匹配(对于每个男生每次循环都要初始化这个数组为false)
void add(int a,int b)
{
e[idx] = b;
ne[idx] = head[a];
head[a] = idx;
idx++;
}
bool find(int x) //表示x号男生是否能够找到对象
{
for(int i=head[x];i!=-1;i=ne[i])
{
int j = e[i];
if(!st[j]) //如果x号男生中意的女生还没有尝试发出请求过,就尝试发出请求看看能不能匹配
{
st[j] = true;
if(match[j]==0 || find(match[j])) //看看中意的这个女生如果没有对象或者这个女生现在的对象可以找到其他的对象
{ // 那就让这个女生的现在的对象去找别的人把这个女生留给当前这个男生
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
memset(head,-1,sizeof head);
cin >> n1 >> n2 >> m;
while(m--)
{
int a,b;
cin >> a >> b;
add(a,b);
}
int res = 0;
for(int i=1;i<=n1;i++)
{
//对于每一个男生初始化其尝试匹配的女生
memset(st,false,sizeof st);
if(find(i)) res++;
}
cout << res << endl;
return 0;
}