- 前向星
- 注意边数开够
int Head[N], Ver[N*2], Next[N*2], Ew[N*2], Gtot=1;
inline void graphinit(int n) {Gtot=1; for(int i=1; i<=n; ++i) Head[i]=0;}
inline void edge(int u, int v, int w=1) {Ver[++Gtot]=v; Next[Gtot]=Head[u]; Ew[Gtot]=w; Head[u]=Gtot;}
#define go(i,st,to) for (int i=Head[st], to=Ver[i]; i; i=Next[i], to=Ver[i])
- 非负权图
namespace DIJK{//适用非负权图 满足当前dist最小的点一定不会再被松弛
typedef pair<long long,int> pii;
long long dist[N];//存最短路长度
bool vis[N];//记录每个点是否被从队列中取出 每个点只需第一次取出时扩展
priority_queue<pii,vector<pii>,greater<pii> >pq;//维护当前dist[]最小值及对应下标 小根堆
inline void dijk(int s,int n){//s是源点 n是点数
while(pq.size())pq.pop();for(int i=1;i<=n;++i)dist[i]=INFLL,vis[i]=0;//所有变量初始化
dist[s]=0;pq.push(make_pair(0,s));
while(pq.size()){
int now=pq.top().second;pq.pop();
if(vis[now])continue;vis[now]=1;
go(i,now,to){
const long long relx(dist[now]+Ew[i]);
if(dist[to]>relx){dist[to]=relx;pq.push(make_pair(dist[to],to));}//松弛
}
}
}
}
- 倍增求lca
- 数组开够
namespace LCA_Log{
int fa[N][22],dep[N];
int t,now;
void dfs(int x){
dep[x]=dep[fa[x][0]]+1;
go(i,x,to){
if(dep[to])continue;
fa[to][0]=x;for(int j=1;j<=t;++j)fa[to][j]=fa[fa[to][j-1]][j-1];
dfs(to);
}
}
//初始化接口
inline void lcainit(int n,int rt){//记得初始化全部变量
now=1;t=0;while(now<n)++t,now<<=1;
for(int i=1;i<=n;++i)dep[i]=0,fa[i][0]=0;
for(int i=1;i<=t;++i)fa[rt][i]=0;
dfs(rt);
}
//求lca接口
inline int lca(int u,int v){
if(dep[u]>dep[v])swap(u,v);
for(int i=t;~i;--i)if(dep[fa[v][i]]>=dep[u])v=fa[v][i];
if(u==v)return u;
for(int i=t;~i;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
}
- tarjan
$O(n)$
namespace SCC{
int dfn[N],clk,low[N];
bool ins[N];int sta[N],tot; //栈 存正在构建的强连通块
vector<int>scc[N];int c[N],cnt;//cnt为强联通块数 scc[i]存放每个块内点 c[i]为原图每个结点属于的块
void dfs(int x){
dfn[x]=low[x]=(++clk);//low[]在这里初始化
ins[x]=1;sta[++tot]=x;
go(i,x,to){
if(!dfn[to]){dfs(to);low[x]=min(low[x],low[to]);}//走树边
else if(ins[to])low[x]=min(low[x],dfn[to]);//走返祖边
}
if(dfn[x]==low[x]){//该结点为块的代表元
++cnt;int u;
do{u=sta[tot--];ins[u]=0;c[u]=cnt;scc[cnt].push_back(u);}while(x!=u);
}
}
inline void tarjan(int n){//n是点数
for(int i=1;i<=cnt;++i)scc[i].clear();//清除上次的scc 防止被卡MLE
for(int i=1;i<=n;++i)dfn[i]=ins[i]=0;tot=clk=cnt=0;//全部变量初始化
for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
for(int i=1;i<=n;++i)c[i]+=n;//此行(可以省略)便于原图上加点建新图 加新点前要初始化Head[]=0
}
}
- 复杂度O(nm)
#include<bits/stdc++.h>
using namespace std;
namespace Hungary{
const int N=1000009;
//图中应储存左部到右部的边 左点编号1..n 右点编号1..m
int Head[N],Ver[N*2],Next[N*2],Gtot=1; //注意边数开够
inline void graphinit(int n){Gtot=1;for(int i=1;i<=n;++i)Head[i]=0;}
inline void edge(int u,int v){Ver[++Gtot]=v,Next[Gtot]=Head[u],Head[u]=Gtot;}
#define go(i,st,to) for(int i=Head[st],to=Ver[i];i;i=Next[i],to=Ver[i]) //st是左点,to是st能到达的右点
int match[N],vis[N];//右点的的匹配点和访问标记
bool dfs(int x){//x是左点
go(i,x,to)if(!vis[to]){
vis[to]=1;
if(!match[to]||dfs(match[to])){
match[to]=x;return 1;
}
}
return 0;
}
inline int ask(int n,int m){//左点数和右点数 返回最大匹配数
for(int i=1;i<=m;++i)match[i]=0;int res=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)vis[j]=0;
res+=dfs(i);
}
return res;
}
}
-
要求该图存在完美匹配 该算法将最大化完美匹配的权值和
-
复杂度 O(n^3)
-
naive的写法复杂度为O((n^2)m) 在完全图上会退化至O(n^4) luogu P6577
#include<bits/stdc++.h>
using namespace std;
namespace KM{
const int N=509;
const long long INFLL=0x3f3f3f3f3f3f3f3fll;//注意INF应足够大 至少远大于n*n*Max{W}
int n;//左右点数均为n 标号均为1..n
int w[N][N];//邻接矩阵存边权
bool r[N][N];//记录i j之间是否有边连接 完全图/非0权图则不必要
inline void init(){//记得重写初始化
int m;scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int u,v,wt;scanf("%d%d%d",&u,&v,&wt);
w[u][v]=wt;r[u][v]=1;
}
}
long long la[N],lb[N],upd[N];//左右顶标 每个右点对应的最小delta 要开longlong
int last[N];//每个右点对应的回溯右点
bool va[N],vb[N];
int match[N];//每个右点对应的左匹配点
bool dfs(int x,int fa){
va[x]=1;
for(int y=1;y<=n;++y)if(r[x][y]&&!vb[y]){
const long long dt=la[x]+lb[y]-w[x][y];
if(dt==0){//相等子图
vb[y]=1;last[y]=fa;
if(!match[y]||dfs(match[y],y)){
match[y]=x;
return 1;
}
}else if(upd[y]>dt){//下次dfs直接从最小delta处开始
upd[y]=dt;
last[y]=fa;//用last回溯该右点的上一个右点以更新增广路
}
}
return 0;
}
inline void KM(){
for(int i=1;i<=n;++i){//初始化顶标
la[i]=-INFLL;lb[i]=0;
for(int j=1;j<=n;++j)if(r[i][j])la[i]=max(la[i],(long long)w[i][j]);
}
for(int i=1;i<=n;++i){//尝试给每一个左点匹配上右点 匹配失败则扩展相等子图重试至成功
memset(va,0,sizeof va);//注意复杂度
memset(vb,0,sizeof vb);
memset(last,0,sizeof last);
memset(upd,0x3f,sizeof upd);
int st=0;match[0]=i;//给起点i连一个虚右点 标号为0
//不断尝试将右点st从已有的匹配中解放 以获得增广路
while(match[st]){//当st到达非匹配右点直接退出
long long delta=INFLL;
if(dfs(match[st],st))break;//st的左点匹配到了新的右点则退出
for(int j=1;j<=n;++j)
if(!vb[j]&&upd[j]<delta){//下次从最小的delta处开始DFS
delta=upd[j];
st=j;
}
for(int j=1;j<=n;++j){//将交错树上的左顶标加delta 右顶标减delta 使更多的边转为相等边
if(va[j])la[j]-=delta;
if(vb[j])lb[j]+=delta;
else upd[j]-=delta;//小问题:这里在干啥 每次修改顶标后重新计算upd是否可行
}
vb[st]=1;
}
while(st){//更新增广路
match[st]=match[last[st]];
st=last[st];
}
}
long long ans=0;
for(int i=1;i<=n;++i)ans+=w[match[i]][i];
printf("%lld\n",ans);
for(int i=1;i<=n;++i)printf("%d%c",match[i]," \n"[i==n]);
}
//signed main(){init();KM();return 0;}
}