一.最短路
1.1单源最短路bellman_ford算法
/*
复杂度O(VE)
可以处理负边权图,并且判断是否存在负权回路
vector < edge > g 存边需要先g.clear();然后g.push(from,to,cost)添加边
点的编号从1开始
*/
const int N=2010;
const int INF=0x3f3f3f3f;
struct edge
{
int from,to,val;
edge(int x=0,int y=0,int z=0)
{
from=x;
to=y;
val=z;
}
};
int dp[N];
vector<edge>g;
bool bellman(int s,int n)//返回false表示有负环回路
{
for(int i=1; i<=n; i++)dp[i]=INF;
dp[s]=0;
for(int i=1; i<n; i++)
{
bool falg=true;
for(int j=0; j<g.size(); j++)
{
int u=g[j].from,v=g[j].to,cost=g[j].val;
if(dp[v]>dp[u]+cost)
{
dp[v]=dp[u]+cost;
falg=false;
}
}
if(falg)return true;
}
for(int j=0; j<g.size(); j++)
{
int u=g[j].from,v=g[j].to,cost=g[j].val;
if(dp[v]>dp[u]+cost)return false;
}
return true;
}
二、联通分量
2.1 有向图的强连通分量
/*
1.调用时先初始化 用add(x,y)增加边,调用solve()后才能缩点 //已测试三题 AC 代码确保无误
2.N是点数 M是边数 (点是从1到n),注意M大小。
3.bcc 记录第i个连通分量的点 belong表示点属于哪个连通分量 sent表示联通分量个数
4.g是缩点后的树
5.du记录缩点后的图的每个联通分支出度(可能会有很多重复边)
*/
const int N=10010;
const int M=100100;
vector<int> bcc[N],g[N];
bool insta[N];
int head[N],dfn[N],low[N],sta[N],belong[N],du[N];
int nu,top,tot,sent,n,m;
struct edge
{
int from,to,nex;
}e[M];
void add(int from,int to)
{
e[nu].from=from;
e[nu].to=to;
e[nu].nex=head[from];
head[from]=nu++;
}
void tarjan(int x)
{
low[x]=dfn[x]=++tot;
sta[top++]=x;
insta[x]=true;
for(int i=head[x];i!=-1;i=e[i].nex)
{
int y=e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(insta[y]&&low[x]>dfn[y])
low[x]=dfn[y];
}
if(low[x]==dfn[x])
{
int t;
sent++;
bcc[sent].clear();
do
{
t=sta[--top];
insta[t]=false;
belong[t]=sent;
bcc[sent].push_back(t);
}while(t!=x);
}
}
void init()
{
nu=1;
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(insta,false,sizeof(insta));
memset(du,0,sizeof(du));
}
void solve()
{ sent=top=tot=0;
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
}
void suodian()
{
int x,y;
for(int i=1;i<=sent;i++)g[i].clear();
for(int i=1;i<nu;i++)
{
x=belong[e[i].from],y=belong[e[i].to];
if(x==y)continue;
g[x].push_back(y);
du[x]++;
}
}
三、二分图匹配
1).一个二分图中的最大匹配数等于这个图中的最小点覆盖数
König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。
2).最小路径覆盖=最小路径覆盖=|G|-最大匹配数
在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点,
且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,
那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通子集.
由上面可以得出:
1.一个单独的顶点是一条路径;
2.如果存在一路径p1,p2,……pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,……pk不再与其它的
顶点之间存在有向边.
最小路径覆盖就是找出最小的路径条数,使之成为G的一个路径覆盖.
路径覆盖与二分图匹配的关系:最小路径覆盖=|G|-最大匹配数;
3).二分图最大独立集=顶点数-二分图最大匹配
独立集:图中任意两个顶点都不相连的顶点集合。
3.1 匈牙利算法
/*
1.N是点数,下标1-n
2.用g.push_back建图后,直接调用max_macth()得到最大二分匹配
3.最终,liky表示与“y轴”上i匹配的值。
4.用DFS不断增加新的匹配
5.注意手动初始化
*/
const int N=1000;
vector<int> g[N];
int lik[N],n,m;
bool visy[N];
bool dfs(int x)
{
for(int i=0;i<g[x].size();i++)
{
int y=g[x][i];
if(visy[y])continue;
visy[y]=true;
if(lik[y]==-1||dfs(lik[y]))
{
lik[y]=x;
return true;
}
}
return false;
}
int max_match()
{
int ret=0;
memset(lik,-1,sizeof(lik));
for(int i=1;i<=n;i++)
{
memset(visy,false,sizeof(visy));
if(dfs(i))ret++;
}
return ret;
}
3.2.HK算法(优化的匈牙利算法)
/*
1.N表示点数,下标1-n,一般N超过300才用HK算法
2.直接调用max_macth()得到答案
3.my和mx存储最终mx和my匹配的点
*/
const int N=1000;
vector<int> g[N];
int mx[N],my[N],dx[N],dy[N],dis,n,m;
bool vis[N];
int bfs()
{
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
queue<int> que;
for(int i=1;i<=n;i++)
if(mx[i]==-1)
{
que.push(i);
dx[i]=0;
}
while(!que.empty())
{
int x=que.front();que.pop();
if(dx[x]>dis)break;
for(int i=0;i<g[x].size();i++)
{ int y=g[x][i];
if(dy[y]!=-1)continue;
dy[y]=dx[x]+1;
if(my[y]==-1)dis=dy[y];
else
{
dx[my[y]]=dy[y]+1;
que.push(my[y]);
}
}
}
return dis!=INF;
}
int dfs(int x)
{
for(int i=0;i<g[x].size();i++)
{int y=g[x][i];
if(vis[y]||dy[y]!=dx[x]+1)continue;
vis[y]=true;
if(my[y]!=-1&&dy[y]==dis)continue;
if(my[y]==-1||dfs(my[y]))
{
my[y]=x;
mx[x]=y;
return 1;
}
}
return 0;
}
int max_macth()
{
int ret=0;
memset(my,-1,sizeof(my));
memset(mx,-1,sizeof(mx));
while(bfs())
{
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)
if(mx[i]==-1&&dfs(i))ret++;
}
return ret;
}