算法基础课 第三章

搜索算法

DFS
全排列

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

int ar[10],vis[10];
int n;

void dfs(int x)
{
	if (x==n+1)
	{
		for (int i=1;i<=n;i++)
		{
			printf("%d ",ar[i]);
		}
		printf("\n");return ;
	}
	for (int i=1;i<=n;i++)
	{
		if (!vis[i])
		{
			ar[x]=i;
			vis[i]=1;
			dfs(x+1);
			vis[i]=0;
		}
	}
}
int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

八皇后

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

int ar[10],vis[10];
int n;
void print()
{
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++)
		{
			if (j==ar[i])cout<<"Q";
			else cout<<".";
		}
		cout<<endl;
	}
	cout<<endl;
}
void dfs(int k)
{
	if (k==n+1)
	{
		print();
		return ;
	}
	for (int i=1;i<=n;i++)
	{
		int j=0;
		for (j=1;j<=k;j++)
		{
			if (i==ar[j]||abs(k-j)==abs(i-ar[j]))break;
		}
		if (j==k+1)
		{
			ar[k]=i;
			dfs(k+1);
			ar[k]=0;
		}
	}
}
int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

BFS
走迷宫

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e2+10;
int ar[N][N],dis[N][N];
int a[]={-1,1,0,0};
int b[]={0,0,-1,1};
int p[N*N],q[N*N];
int n,m;

void bfs(int x,int y)
{
	int head=0,top=0;
	p[top]=x;q[top++]=y;
	
	int xx,yy;
	
	while(head<=top)
	{
		x=p[head];y=q[head++];
		if (!ar[x][y])
		{
			for (int i=0;i<4;i++)
			{
				xx=x+a[i],yy=y+b[i];
				if (xx>=1 && xx<=n && yy>=1 && yy<=m
				&& dis[xx][yy]==0 && ar[xx][yy]==0)
				{
					dis[xx][yy]=dis[x][y]+1;
					p[top]=xx;q[top++]=yy;
				}
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	For(i,1,n)
	{
		For(j,1,m)
		{
			cin>>ar[i][j];
			//ar[i][j]=-ar[i][j];
		}
	}
	bfs(1,1);
	cout<<dis[n][m]<<endl;
	
	return 0;
}

最短路算法

dijkstra

朴素版本的dijkstra 复杂度 N^2 适用于稠密图
一共就n个点,两两之间的距离进行尝试,最多n^2次的循环

利用贪心的思想
外层循环n-1次
每一次找一个最小的点进行距离更新
内层循环,先找到最近的点
然后利用这个点进行更新

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e3+10;
int g[N][N];
int vis[N];
int n,m;
int a,b,c;

void dijkstra()
{
	vis[1]=1;
	for (int i=1;i<=n;i++)
	{
		int t=0;
		for (int j=1;j<=n;j++)
		{
			if (!vis[j] && g[1][j]<g[1][t])
			{
				t=j;
			}
		}
		if (t && g[1][t]==B)return;
		vis[t]=1;
		for (int j=1;j<=n;j++)
		{
			g[1][j]=min(g[1][j],g[1][t]+g[t][j]);
		}
	}
}

int main()
{
	memset(g,0x3f,sizeof g);
	
	cin>>n>>m;
	For(i,1,m)
	{
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c);
	}
	dijkstra();
	if (g[1][n]!=B)cout<<g[1][n];
	else cout<<-1;
	return 0;
}

优先队列优化后的 dijkstra
复杂度 M*log M 适用于稀疏图
每次找到一条最短的边,在堆查找的情况下,花费log M
一共m 条边,最差的情况,每一条边都需要查找,那么查找m次

每一次查找得到一条边的长度,与一个点
判断这个点是否进行过查找,如果已经进行过,那么查找下一条边
如果没有进行过,那么查找这条边的每一个出边
遍历完所有出边,并且尝试更新距离

//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<bits/stdc++.h>
#define pb push_back
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e6+10;
int h[N],e[N],w[N],ne[N],idx;
int n,m,dist[N],vis[N];

priority_queue<PII, vector<PII> , greater<PII> > heap;


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

void dijkstra()
{
	vis[1]=0;// 初始化的时候, 起始点 的dist 需要 = 0 ,
	//   而 vis 不能变成 1 ,需要在后面,遍历完 起始点的所有 出边后, 再赋值为 1,不再访问
	dist[1]=0;
	heap.push({0,1});
	
	while(heap.size())
	{
		auto t=heap.top();
		heap.pop();
		
		int po=t.second,dis=t.first;// 利用小根堆优化
		// dis 是最短距离 
		// po 是对应点的下标
		
		// 剪枝
		if (vis[po])continue;// 如果已经访问过,那么就不需要再次访问,也就是重边只取最短的一条
		
		vis[po]=1;
		
		if (vis[n])return ;// 如果终点已经被找到,那么直接退出
		
		for (int i=h[po];i!=-1;i=ne[i])// 序号遍历,也就是遍历 po 的每一条出边
		{
			int j=e[i];// 找到下标 
			
			if (dist[j]>dist[po] + w[i]) // 判断距离,是否需要更新
			{
				dist[j]=dist[po]+w[i];
				heap.push({dist[j],j});
			}
		}
	}
}

int main()
{
	memset(h,-1,sizeof h);
	memset(dist,0x3f,sizeof dist);
	int a,b,c;
	cin>>n>>m;
	
	For(i,1,m)
	{
		cin>>a>>b>>c;
		add(a,b,c);
	}
	dijkstra();
	if (dist[n]!=B)
	{
		cout<<dist[n];
	}
	else cout<<-1;
	return 0;
}

bellman_ford 算法
计算k 条边所能够到达的最短路
复杂度 NM (或者说 KM )
因为最多判断n次,再重复判断就没有意义了

思路,两层循环,外层表示第i 次遍历
内层表示,第几条边

还需要使用back 备份数组,防止一条边在一次循环过程中,被反复利用
类似完全背包的模样

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e3+10;
const int M=1e5+10;
int back[N],dist[N];
typedef struct{
	int a,b,c;
}edge;

edge ar[M];

int n,m,k;

void bellman_ford()
{
	dist[1]=0;
	
	for (int i=1;i<=k;i++)
	{
		memcpy(back,dist,sizeof dist);
		for (int j=1;j<=m;j++)
		{
			int a=ar[j].a,b=ar[j].b ,c=ar[j].c ;
			dist[b]=min(dist[b],back[a]+c);
			//cout<<b<<" "<<dist[b]<<endl;
			
		}
	}
}
int main()
{
	int a,b,c;
	
	cin>>n>>m>>k;
	memset(dist,0x3f,sizeof dist);
	
//	cout<<n<<" "<<m<<" "<<k<<endl;
	
	For(i,1,m)
	{
		cin>>a>>b>>c;
		ar[i]={a,b,c};
	}
	bellman_ford();
	//for (int i=1;i<=n;i++)cout<<dist[i]<<" ";
	
	if (dist[n]>B/2)cout<<"impossible";
	else cout<<dist[n];
	return 0;
}


SPFA 算法
复杂度 理论上是N*M , 但是一般效果较好,可能接近于 o(N)

思路有点类似于dijkstra

一个是优先队列,一个是队列
dijkstra 中, st[] 数组表示的是,这个点是否被访问过了
而spfa 中,st[] 数组表示,这个点是否在队列中
需要注意的是,这里的队列,可以不断进出,而st[i] 可以变成1也可以清零

因为,一个点的距离被更新时,他就有可能会使得最终结果更优
那么,这个点应该被加入队列中,进行查找
但是,有可能一个点可以被更新多次,
比如说,距离从 10 -> 5 -> 3
我们在 10-> 5 这一步,就会把点入队,同时将 st[x]=1
当进行到 5->3 时,会将距离更新为 3
如果此时点x 依然在数组中,那么保持不变
否则,将x点再次入队
直到队列为空

SPFA

利用队列进行, 手动模拟邻接表

关键在于,不断更新每一个点的距离,有些类似dijkstra

//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e5+10;
int h[N],st[N],dist[N],e[N],w[N],ne[N];
int n,m,idx;
void add(int a,int b,int c)
{
	e[idx]=b;
	w[idx]=c;
	ne[idx]=h[a];
	h[a]=idx++;
}
void spfa()
{
	queue<int> q;
	q.push(1);
	dist[1]=0;
	st[1]=0;
	
	while(q.size())
	{
		int t=q.front();
		q.pop();
		st[t]=0;
		
		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])
				{
				// 如果这个点被更新了,那么他就有可能将他的出边也进行更新
				//如果这个点并不在队列之中,那么将他放入队列里
					st[j]=1;
					q.push(j);
				}
			}
		}
	}
}
int main()
{
	memset(dist,0x3f,sizeof dist);
	memset(h,-1,sizeof h);
	
	cin>>n>>m;

	For(i,1,m)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	spfa();
	if (dist[n]==B)
	{
		cout<<"impossible";
	}
	else
	{
		cout<<dist[n];
	}
	return 0;
}

SPFA算法 判断存在负环

spfa_

用于判断 图中是否存在负环

1, 存在正的环并没有影响,只会越走越长,不需要考虑,因此一般说的环都是负环
2, 存在负的环,需要更新的是,走到每一个点的最短距离需要的边数 cnt[i]
3, 如果存在 cnt[x] >=n , 也就是说,一共n个点, 走 n 步,那肯定存在重复的路径,也就是环路。n个点最多只能走n-1步,
4, 之前的SPFA 是判断1~n的最短路,如果只是增加cnt【】,没有将每一个点都加入队列之中,判断的结果是 1~n的最短路径上,是否存在负环。
加入每一个点之后,判断的是 图中是否存在负环

一开始的时候,需要将每一个点都放入队列中,因为环路不一定存在于最短路径上
比如说,6个点 , 1 -> 3 -> 6 就是最短路径,只需要2条边就可以达到,没有环。
但是, 可能 2-> 4 -> 2 之间存在负环,或者5->5(存在负的自环),因此,初始时需要将所有点都放入队列

5, 初始化的问题,在spfa 求最短路的时候,需要将起始点 dist[1] = 0
而在求是否存在负环的时候, 由于路径有可能从任意一个点开始,那么就不能将dist[i] 变成 0

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e3+10;
const int M=1e5+10;
int h[M],e[M],ne[M],w[M],st[M],dist[M],cnt[M];
int n,m,idx;
queue<int> q;

void add(int a,int b,int c)
{
	w[idx]=c;e[idx]=b;ne[idx]=h[a];
	h[a]=idx++;
}
void spfa_()
{
	while(q.size())
	{
		int t=q.front();q.pop();
		st[t]=0;
		
		for (int i=h[t];i!=-1;i=ne[i])
		{
			int j=e[i];
			if (dist[j]>dist[t]+w[i])
			{
				cnt[j]=cnt[t]+1;
				if (cnt[j]==n)
				{
					cout<<"Yes\n";exit(0);
				}
				dist[j]=dist[t]+w[i];
				if (!st[j])
				{
					st[j]=1;
					q.push(j);
				}
			}
		}
	}
	cout<<"No\n";
}
int main()
{
	int a,b,c;
	cin>>n>>m;
	memset(dist,0x3f,sizeof dist);
	memset(h,-1,sizeof h);
	
	For(i,1,m)
	{
		cin>>a>>b>>c;
		add(a,b,c);
	}
	for (int i=1;i<=n;i++)
	{
		q.push(i);st[i]=1;
	}
	
	spfa_();
	
	return 0;
}


Floyd 算法
求多源最短路 复杂度 o(N^3)
有点DP的味道…

dp[i][j] = min dp[i][j], dp[i][k]+dp[k][j]

就是说, 从 i -> j 的花费 ,被 更新为 从 i -> k 再从 k -> j

需要注意循环的顺序,K,I,J
最外层,表示,利用k点作为中转点,尝试进行更新
,更新的方式就是,如果从 i->j的花费,大于 i->k -> j 从i到k中转,再到 j
那么就可以进行更新

这样的循环顺序,可以保证每两点之间的距离,被不断地更新
先利用第1个点, 更新 2~n 之间的距离
1用完了,再尝试 2 ,更新 1,3~n之间的距离
直到每一个点作为中转点的方式都被尝试过

相反,如果循环顺序为 i,j,k
在不改变转移方程的情况下,循环的意义会被改变
这种情况下,i,j 在外层
举例子, 1 - 2 的距离会被 3~n的中转点更新
但是,只会在第一次更新,后面的循环中,再也见不到 1 - 2 这两个点
这是不合理的, 因为在不断的循环过程中 , 3~n的点也会得到更新
可能更新过后,会使得距离更优,而不是一开始刚刚赋值的情况,因此这是错误的顺序

#include<bits/stdc++.h>

using namespace std;

const int N=1e3+10;
int g[N][N];
int n,m,k;

void floyd()
{
	for (int k=1;k<=n;k++)
	{
		for (int i=1;i<=n;i++)
		{
			for (int j=1;j<=n;j++)
			{
				g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
			}
		}
	}
}
int main()
{
	int a,b,c;
	memset(g,0x3f,sizeof g);
	
	cin>>n>>m>>k;
	while(m--)
	{
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c);
	}
	// 初始化的时候需要注意, 这里需要赋值为 0 
	for (int i=1;i<=n;i++)
	{
		g[i][i]=0;
	}
	
	floyd();
	while(k--)
	{
		cin>>a>>b;
		if (g[a][b]*2 > g[0][0])
		{
			cout<<"impossible\n";
		}
		else
		{
			cout<<g[a][b]<<"\n";
		}
	}
	
	return 0;
}

利用 Floyd 算法 计算最小环

最小环 指的是, 图中,权重最小的环

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=1e3+10;
int g[N][N];
int mp[N][N];
int n,m,a,b,c;
int ans;

void floyd_circle()
{
	ans=B;
	
	for (int k=1;k<=n;k++)
	{
	//  注意i,j ,的遍历范围
	
		for (int i=1;i<k-1;i++)
		{
			for (int j=i+1;j<k;j++)
			{
			// 最小环的结果,为 
			// 以 k  为中转点时, 从 i - > j 的最小距离,然后再加上, j->k , k->i 这两条边的长度
			// 因为一开始能够得到最小距离, 所以证明, 在不经过 k 的情况下,  i -> j 之间就存在边
			// 那么,多加上 两条边 j->k . k->i  就形成了环 
			// 因此, 这就是最小环
			// 需要注意, 最短距离保存在 g[][] 数组中, 就是原本的 Floyd 数组
			// 而两条边的权重 , 保存在 mp [] [] 数组中
				ans=min(ans,g[i][j]+mp[j][k]+mp[k][i]);
			}
		}
		
		for (int i=1;i<=n;i++)
		{
			for (int j=1;j<=n;j++)
			{
				g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
			}
		}
	}
}
int main()
{
	memset(g,0x3f,sizeof g);
	cin>>n>>m;
	For(i,1,m)
	{
		cin>>a>>b>>c;
		g[a][b]=g[b][a]=min(g[a][b],c);
	}
	For(i,1,n)g[i][i]=0;
	For(i,1,n)
	{
		For(j,1,n)
		{
			mp[i][j]=g[i][j];
		}
	}
	floyd_circle();
	
	cout<<ans;
	
	return 0;
}
/*
5 7
1 2 6
1 3 1
2 1 1
1 4 1
2 4 4
2 5 1
4 5 1

一共 5 个点, 7 条边
带权边如上
求得 最小环 为 5 .

*/

最短路总结

dijkstra, 堆优化的dijkstra , bellman_ford, SPFA , floyd
一共有 5 种算法,分别用在不同的情况

按照类型,分为
单源最短路 , 多源最短路 (Floyd)

单源中,按照边权的情况

  • 1 都是正的边权, 采用dijkstra
    朴素版本的dijkstra ,复杂度是o N^2 ,适合稠密图 , M~N^2 , N <=5e3
    而堆优化版本的dijkstra , 复杂度 M*logM , 适合稀疏图, M < 1e5

  • 2 图中存在的边,甚至可能出现负环
    当且仅当,出现 k 条边的限制时 , 采用 bellman_ford 算法
    其他情况下, 优先采用 SPFA 算法

最短路的代码分析
1 朴素版本 dijkstra , floyd 都是直接采用邻接矩阵
便于书写,同时也便于直接访问每一条边

2 堆优化dijkstra ,
利用pair<int,int> , PII
priority_queue<PII, vector , greater > heap

直接利用, pair 进行排序,按照第一关键字,第二关键字进行

3 ballman_ford , SPFA
都采用 结构体的方式, 直接得到每一条边的端点与距离

最小生成树

一共有两种 prim , kruskal

prim算法
有点类似于 朴素版dijkstra , 复杂度为 N^2 ,适用于稠密图

利用返回值,判断最后的结果
如果返回值为B , 说明prim算法不是正常结束的
相反,说明算法正常运行,得到的结果就是最小生成树

思路类似于 dijkstra , 用于稠密图,复杂度为 o(N^2)

代码的细节
距离的初始化,注意,这里是生成树,没有特别的起始点,从任意一个点开始都可以

这种朴素的算法,多用于稠密图,但是n^2,就有可能出现 MLE
遇到的问题
洛谷 1265

解决方法是,不要在一开始预处理的时候,利用g[N][N] 来记录
而是在 prim() 中, 根据 t , j 进行更新

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=5e2+10;
const int M=1e5+10;

int g[N][N],st[N],dist[N];

int n,m;
int a,b,c,ans;

void prim()
{
	int t=0;
	ans=0;
	
	for (int i=0;i<n;i++)
	{
		t=0;
		for (int j=1;j<=n;j++)
		{
			if (!st [j] && (t==0 ||dist[j]<dist[t]))
			{
				t=j;
			}
		}
		if (i && dist[t]==B)return ;
		
		if (i)ans+=dist[t];
		st[t]=1;
		
		
		for (int j=1;j<=n;j++)
		{
			dist[j]=min(dist[j],g[t][j]);
		}
	}
}
int main()
{
	memset(g,0x3f,sizeof g);
	memset(dist,0x3f,sizeof dist);
	cin>>n>>m;
	For(i,1,m)
	{
		cin>>a>>b>>c;
		g[a][b]=g[b][a]=min(g[a][b],c);
	}
	prim();
	if (ans==B)cout<<"impossible";
	else cout<<ans;
	return 0;
}

kruskal 算法
复杂度 M*logM 适用于 稀疏图

先将边 按照距离排序
然后从最小的边开始连接 使得总的费用最小
其中,需要利用并查集, 判断两个点是否已经连通了
如果已经连通,再加上一条边,那么一定会出现环,因此舍去这条更长的边
否则, 将两个点连通,边数增加,总费用增加

利用边数 cnt == n-1 判断是否生成了合理的最小生成树
根据返回值得到相应的结果

要多多理解
然后有一些细节

排序的时候,使用 cmp()+sort 的做法
但是要注意, 如果 两条边相等的情况,
如果权重相等,那么就不需要变换位置,因为他们的效果是一样的
而且,如果选择变换位置,那么当数据的权重都一样时, 就会进行 n^2次变换
使得超时

if (a1.w < a2.w)
{
return 1;
}
return 0;

所以说, pair<int, pair<int,int> > ar[N]
才是最为方便的写法hh

#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=5e2+10;
const int M=1e5+10;

typedef struct{
	int a,b,w;
}edge;
struct cmp{
	bool operator() (const edge&a1,const edge&a2)
	{
		return a1.w<a2.w;
	}
};
edge ar[M];
int n,m,a,b,c,ans;
int p[M];

int find(int x)
{
	if (p[x]!=x)p[x]=find(p[x]);
	return p[x];
}
int kruskal()
{
	ans=0;int cnt=0;
	For(i,1,m)
	{
		a=ar[i].a,b=ar[i].b,c=ar[i].w;
		int x=find(a),y=find(b);
		if (x!=y)
		{
			p[x]=y;cnt++;
			ans+=c;
			if (cnt==n-1)return ans;
		}
	}
	
	return B;
}
int main()
{
	cin>>n>>m;
	For(i,1,m)
	{
		cin>>a>>b>>c;
		ar[i]={a,b,c};
		p[i]=i;
	}

	sort(ar+1,ar+m+1,cmp());
	ans = kruskal();
	
	if (ans==B)cout<<"impossible";
	else cout<<ans;
	return 0;
}

剩下的一些内容先欠着,上课去
下面是二分图的一些内容

第一个问题 ,判断一个图,是否是二分图
利用染色法进行判断
关键在于 bool dfs(int u, int c )
这个函数

首先,如果一个点进行过染色, color[i] = 0
那么,进行染色,染成 1
然后 颜色1这个点的所有出边都染成 -1
同样的, 颜色 -1 点的所有出边染色为 1

这就是核心的思路 , 如果一个点没有染色,那么将他染成 1
如果一个点染色了,那么对于他的每一个出边,进行 染色, 染成与该点不同的颜色
1 — >> - 1 , -1 ---->> 1

染色的过程中,有两种情况。
1 如果一个点要进行染色,如果他的出边没有被染色过,进行染色
2 如果他的出边已经被染色, 那么就需要对颜色进行判断
如果两种颜色相同 即 color[ j ] == c 说明一条边的两个点的颜色是一样的
这样就违背了二分图的要求, 直接返回 false

二分图的判定关键 是否存在 奇数环

当且仅当一个图中,不存在奇数环,这个图是 二分图

二分图的定义 : 一个图,所有点分成2类,左右两个连通块

边只存在于 两个连通块之间,连通块内部不能有边

染色法判定二分图 思路

给每一个没有被染色的点赋值为 1 ,第一种颜色
1 的 所有出边 赋值为 2 ,第二种颜色
利用 dfs, 递归地进行染色, 2的出边染成 1

//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=2e5+10;
int h[N],e[N],ne[N],color[N];
int n,m,idx;

void add(int a,int 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]) // 如果一个点没有被染过
		{
		// 那么进行染色
		// 注意这里的 3-c 
		// 因为颜色只有 1 和 2
		// 1->2 , 2->1 ,两者之间的关系就是 3-c
		
			if (!dfs(j,3-c))
			{
				return false;  //一样的,利用返回值来判断是否出现奇数环
			}
		}
		
		else if (color[j] == c)//另一种情况
		{
		//可能这个点已经被染色了, 并且,i 与 j 的颜色相同,这是不被允许的
		// 一条边的两个端点必须保证是不同的颜色
		// 因此,直接返回 false
			return false;
		}
		
	}
	// 没有任何不良情况出现,那么返回 true
	return true;
}
int main()
{
	memset(h,-1,sizeof h);
	cin>>n>>m;
	int a,b;
	
	//无向图
	while(m--)
	{
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	
	bool f=true;
	
	// 要遍历所有的点,因为可能不是连通图
	// 在连通的角度,与染色的角度,是不一样的

	
	for (int i=1;i<=n;i++)
	{
		if (!color[i])//没有被染色
		{
		//dfs(i,1) 意味着将 i 号点染成 1 的颜色
		// color[i]= 1; 
		
		//根据返回值,如果为 false 说明不是二分图,直接break结束循环
			if (!dfs(i,1))
			{
				f=false;break;
			}
			//dfs(i,1);
		}
	}
	if (f)
	{
		cout<<"Yes\n";
	}
	else
	{
		cout<<"No\n";
	}
	return 0;
}

二分图的最大匹配

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

匈牙利算法
**理论复杂度 o(N1M) **
** 注意 复杂度不是 o(N1
N2) **

//需要遍历每一对男女(?) 这句话是错误的
//需要遍历二分图中的每一条边 这才是对的

匈牙利的思路是 遍历左边,每一个左边的点都需要进行查找
每一次查找需要 o(M) , 左边一共 o(N1)个点

感觉复杂度很怪 不清楚为啥

复杂度为 o(n1*m+n2)

左边n1 * 边数m + 右边n2

但是,实际情况的复杂度会比较优秀,达不到最坏的复杂度

分析易得,当 n1 > n2 时,将n1,n2交换,可以进行小优化
优化效果为 (n1-n2)*m

//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<bits/stdc++.h>
#define endl "\n"
#define B 0x3f3f3f3f
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long int ll;
typedef pair<int,int> PII;

const int N=2e5+10;
int h[N],e[N],ne[N],color[N],match[N];
int n1,n2,m,idx,st[N];

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

//核心函数 
// find 函数的意思是,二分图中, 对于左边的点x, 能否在右边找到点进行匹配

bool find(int x)
{
	for (int i=h[x];i!=-1;i=ne[i])
	//遍历能够找到的每一条边
	{
		int j=e[i];
		if (!st[j])//如果这个点还没有被访问过
		{
			st[j]=true;//标记为访问
			
			if (match[j]==0 || find(match[j]))
			// 能够匹配只有两种情况
			//1 match[]== 0 , 也就是这个女生并没有被匹配
			
			//重点
			//2 如果match [i] = k (!=0)  ,说明这个女生的对象就是 k
			// 如果,find(match[j]) 说明,这个女生的前男友能够找到新的对象
			//注意,经过find (mathc[j])  前男友k 已经找到了新的对象,因为这个女生 j 已经被标记为访问过了
			// st[j]=true 之前就已经完成
			// 而前男友都找到对象了,那这个女生j,就可以和x 在一起啦hh
			{
				match[j]=x;//j 与 x 进行了成功的匹配
				return true;//返回成功的结果
			}
		}
	}
	//如果,这个男生x的每一条出边,都不能进行成功的匹配
	//那么说明该男生匹配失败,返回失败的结果false
	return false;
}
int main()
{
	memset(h,-1,sizeof h);
	cin>>n1>>n2>>m;
	int a,b;
	while(m--)
	{
		cin>>a>>b;
		add(a,b);
	}
	
	int ans=0;
	
	for (int i=1;i<=n1;i++)
	{
		memset(st,0,sizeof st);
		// 每一次查询,需要将前一次的查找给清空
		// 也就是说,每一个女生都是可以被查找的
		
		if (find(i))//如果,第i个人找到了对象,那么总数增加1
		{
			ans++;
		}
	}
	cout<<ans;
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值