搜索算法
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(N1N2) **
//需要遍历每一对男女(?) 这句话是错误的
//需要遍历二分图中的每一条边 这才是对的
匈牙利的思路是 遍历左边,每一个左边的点都需要进行查找
每一次查找需要 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;
}