一、匈牙利算法模板
重点
匈牙利算法的时间复杂度为O(ve),其中v为二分图左边的顶点数,e为二分图中边的数目。
时间复杂度:邻接矩阵最坏为 O(n^3) 邻接表:O(mn)
空间复杂度:邻接矩阵:O(n^2) 邻接表:O(n+m)
二分图概念: 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(V1,V2),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集,则称图G为一个二分图。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配
6.相关定理
定理1:最大匹配数 = 最小点覆盖数
定理2:最大匹配数 = 最大独立数
定理3:最小路径覆盖数 = 顶点数 - 最大匹配数
A.二分图最大匹配
Description
给定一个二分图,其左部点的个数为 n,右部点的个数为 m,边数为 e,求其最大匹配的边数。
左部点从 11 至 n 编号,右部点从 11 至 m 编号。
Input
输入的第一行是三个整数,分别代表 n,m 和 e。
接下来 e 行,每行两个整数 u,v,表示存在一条连接左部点 u 和右部点 v 的边。
Output
输出一行一个整数,代表二分图最大匹配的边数。
Sample 1
Inputcopy | Outputcopy |
1 1 1 1 1 | 1 |
Sample 2
Inputcopy | Outputcopy |
4 2 7 3 1 1 2 3 2 1 1 4 2 4 1 1 1 | 2 |
Hint
数据规模与约定
对于全部的测试点,保证:
1≤n,m≤500。
1≤e≤5×104。
1≤u≤n,1≤v≤m。
不保证给出的图没有重边。
代码:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N=510;
int map[N][N];//存图
int l[N];//右侧元素所对应的左侧元素
bool vis[N];//标记是否被访问过
int n,m,e,a,b;
bool math(int i)
{
for(int j=1;j<=m;j++)
{
if(map[i][j]&&!vis[j])//如果关系存在,并且未被访问过
{
vis[j]=true;
if(l[j]==0||math(l[j]))//如过暂无匹配,或者原来匹配的左侧元素可以找到新元素
{
l[j]=i;//让当前左侧元素成为右侧元素的新匹配
return true;//匹配成功
}
}
}
return false;//匹配失败
}
int main()
{
cin>>n>>m>>e;
memset(map,0,sizeof(map));
memset(l,0,sizeof(l));
while(e--)
{
cin>>a>>b;
map[a][b]=1;
}
int cnt=0;
for(int i=1;i<=n;i++)
{
memset(vis,false,sizeof(vis));
if(math(i))
cnt++;
}
cout<<cnt<<endl;
return 0;
}
二、迪克斯特拉算法模板(dijkstra)
重点:
1.时间复杂度
没有优化的dijkstra算法的时间复杂度为 O(n^2);
堆优化过的dijkstra算法时间复杂度为O(mlogn);(n表示点数,m表示边数)
2空间复杂度O(n^2),n表示节点个数
3.只适用于有向无环图,如果有负权边,不能使用dijkstra算法
4.dijkstra计算加权图中的最短路径
B.Til the Cows Come Home
史学长很热爱学习,他打算假期偷偷跑回学校学习,为了多学习他希望可以找最快的路线回到学校。 洛阳市里有N个(2 <= N <= 1000)个地铁站,编号分别为1..N。他的家在1号地铁站旁边,洛阳师范学院站是N号地铁站。地铁站之间共有M (1 <= M <= 2000)条双向路径。 史学长现在在1号地铁站,他希望知道到学校最短要多长时间。可以保证史学长能到达学校。忽略史学长在换乘地铁时需要的等待时间。
Input
* 第一行输入两个整数m和n
* 接下来m行,每行三个整数a、b、c,表示a号地铁站和b号地铁站间要花费时间c(1<=c<=100).
Sample
Inputcopy | Outputcopy |
5 5 1 2 20 2 3 30 3 4 20 4 5 20 1 5 100 |
代码:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N=1010;
int p[N][N];//存图
int dis[N];//代表从1到某个点的距离
bool vis[N];//代表访问
int n,m,a,b,c;
int dijkstra()
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[1]=0;//初始化,相当于1到它本身的距离
for(int i=0;i<n-1;i++)//遍历除了1剩下的点
{
int t=-1;//代表中转站
for(int j=1;j<=n;j++)
{
if(!vis[j]&&(t==-1||dis[t]>dis[j]))
{
t=j;//找到1到某个点的最短距离,并更新
}
}
vis[t]=true;//标记,防止再次被访问
for(int j=1;j<=n;j++)
{
dis[j]=min(dis[j],dis[t]+p[t][j]);//从直达和曲达找最短
}
}
return dis[n];
}
int main()
{
cin>>m>>n;//先输入m(多少条路),再输出n
memset(p,0x3f,sizeof(p));
//for(int i=1;i<=n;i++)
//p[i][i]=0;
for(int i=1;i<=m;i++)
{
cin>>a>>b>>c;
p[a][b]=p[b][a]=min(p[a][b],c);//双向路径
}
cout<<dijkstra()<<endl;
return 0;
}
三、拓扑排序
重点
1.对于n个定点,e条弧的有向图而言,时间复杂度:O(n+e)
2.拓扑排序就是在一个有向无环图(也称DAG图),将所有的点排成一个线性的序列,使得每条有向边的起点u都排在终点v的前面。
3在拓扑排序时,判环。所有点都入队说明存在拓扑序列,否则不存在,每次从队列中拿出一个数时,t加1,while循环结束的时候判断t是否等于节点总数,若不等于,则说明图是有环的。
4.如果在同等条件下,想要让编号小的顶点在前面,需要使用优先队列
C.
给出一个图的结构,输出其拓扑排序序列,要求在同等条件下,编号小的顶点在前。
Input
若干行整数,第一行有2个数,分别为顶点数v和弧数a,接下来有a行,每一行有2个数,分别是该条弧所关联的两个顶点编号。
v<=100, a<=500
Output
若干个空格隔开的顶点构成的序列(用小写字母)。
Sample
Inputcopy | Outputcopy |
6 8 1 2 1 3 1 4 3 2 3 5 4 5 6 4 6 5 |
代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N=110;
int du[N];//入度
int n,m,a,b;
vector<int>v[N];
vector<int>ans;
priority_queue<int ,vector<int>, greater<int> >q;//优先队列
void tuopusort()
{
while(!q.empty())
{
int t=q.top();//取出栈顶元素
ans.push_back(t);
q.pop();//弹出
for(int i=0;i<v[t].size();i++)
{
du[v[t][i]]--;//把这个点相关的边删除
if(du[v[t][i]]==0)//在找到下一个入度为0的点
q.push(v[t][i]);
}
}
}
int main()
{
int i;
cin>>n>>m;
for(i=1;i<=m;i++)
{
cin>>a>>b;
v[a].push_back(b);
du[b]++;
}
for(i=1;i<=n;i++)
{
if(du[i]==0)
q.push(i);
}
tuopusort();
for(i=0;i<ans.size();i++)
{
cout<<"v"<<ans[i]<<" ";
}
cout<<endl;
return 0;
}