所有模板的邻接表存图都用的struct{…}e[M];
邻接矩阵(略)、【vector、邻接表/ 链式前向星】
n个点,m条边
// 链式前向星/邻接表
const int M=100007,N=10005;
struct EDGE{
int v,w,nxt;
}e[M];
int head[N],cnt;//memset(head,-1,sizeof(-1));
void addedge(int x,int y,int z){
e[cnt].v=y;
e[cnt].w=z;
e[cnt].nxt=head[x];
head[x]=cnt++;
}
// 遍历now点的所有连点
for(int i=head[now];i!=-1;i=e[i].nxt){
...
}
// vector
const int N=10005;
struct node{int to,cost;};
vector<node>e[N];
inline void addedge(int x,int y,int z){
node t{y,z};
e[x].push_back(t);
}
//遍历now点的连点
for(int i=0;i<e[now].size();i++){
node t=e[now][i];
...
}
//初始化for(int i=1;i<=n;i++)fa[i]=i;
//递归式
int fa[maxn];
int find(int u){
if(fa[u]!=u)fa[u]=find(fa[u]);
return fa[u];
}
//迭代式
int find(int u){
int i=u,fu=u,j;
while(fu!=fa[fu])fu=fa[fu];//查询
while(i!=fu){//路径压缩
j=fa[i];//先记录下i的父亲
fa[i]=fu;//i直接跟祖先相连
i=j; //接下来修改i的父亲节点
}
return fu;
}
//合并
void U(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy)fa[fx]=fy;
}
2.加权并查集
时间复杂度:
kruskal是O(eloge),朴素prime是O(n^2),prime+优先队列是O(eloge)
稀疏图用kruskal,稠密图用prime+heap,空间足够的情况下都用prime+heap。
(范围太大的话就用配对堆?)
#include
using namespace std;
typedef long long ll;
const int N=5007,M=200007;
struct EDGE{int v,w,next;}e[M*2];//存边
struct node{
int v,w;
node(int vv,int ww):v(vv),w(ww){};
bool operator < (const node &o)const{
return w>o.w;
}
};//堆的类型
int n,m,head[N],cnt,vis[N],d[N];//不加d[N]数组大数据可能会TLE
void addedge(int x,int y,int z){
e[cnt].v=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
priority_queue<node>q;
ll prime(int s){
for(int i=1;i<=n;i++)d[i]=0x7fffffff;
ll cost=0;
node t(s,0);
q.push(t);
int Edgecnt=0;
while(!q.empty()&&Edgecnt<=n){
t=q.top();q.pop();
if(vis[t.v])continue;
vis[t.v]=1;
cost+=t.w;
Edgecnt++;
for(int i=head[t.v];i!=-1;i=e[i].next)
if(!vis[e[i].v]&&e[i].w<d[e[i].v]){
q.push(node(e[i].v,e[i].w));
d[e[i].v]=e[i].w;
}
}
if(Edgecnt==n)return cost;
else return -1;//非连通图
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);addedge(y,x,z);
}
ll ans=prime(1);
if(ans!=-1)printf("%lld",ans);else printf("-1");
}
#include
using namespace std;
typedef long long ll;
const int N=5007,M=200007;
struct EDGE{
int u,v,w,next;
bool operator <(const EDGE &o)const{
return w<o.w;
}
}e[M*2];//存边
int n,m,cnt,fa[N];
void addedge(int x,int y,int z){
e[cnt].u=x;
e[cnt].v=y;
e[cnt++].w=z;
}
int find(int u){
if(fa[u]!=u)fa[u]=find(fa[u]);
return fa[u];
}
ll kruskal(){
sort(e,e+cnt);
int Edgecnt=0;
ll cost=0;
for(int i=0;i<cnt;i++){
int fu=find(e[i].u),fv=find(e[i].v);
if(fu!=fv){
cost+=e[i].w;
fa[fu]=fv;
Edgecnt++;
}
if(Edgecnt==n-1)break;
}
if(Edgecnt<n-1)return -1;//非连通
else return cost;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
ll ans=kruskal();
if(ans!=-1)cout<<ans;
else cout<<"orz";
}
多源最短路, 时间O(n^3) ,空间O(n^2)
计算所有点之间的最短路、可以计算负权图 、不能存路径
//邻接矩阵存图。
const ll Inf=0x7fffffff;
ll dis[N][N];
void Floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
int main(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)dis[i][j]=Inf; //初始化
cin>>n;
[...输入邻接矩阵]
Floyd();
}
计算一个点到其他所有点的最短路、适用于无负权图、可以存路径
朴素版时间复杂度O(n^2),,优先队列优化的O(eloge)
//朴素版(邻接表存图)
const int Inf=0x3f3f3f3f;
int n,m,s,t,head[N],vis[N],cnt,dis[N],path[N];//path记录路径
struct EDGE{int v,w,next;}e[M*2];
void Dijkstra(int s){ //s是起点
for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);//初始化
for(int i=1;i<=n;i++){
int now,mn=Inf;
for(int j=1;j<=n;j++)
if(dis[j]<mn&&!vis[j])mn=dis[now=j];
vis[now]=1;
for(int j=head[now];j!=-1;j=e[j].next)
if(dis[e[j].v]>dis[now]+e[j].w){
dis[e[j].v]=dis[now]+e[j].w;
//path[e[j].v]=now; //记录路径
}
}
}
void print_path(int x){//输出最短路径,调用时参数为终点
if(path[x]==0){
printf("%d ",x);
return;
}
print_path(path[x]);
printf("%d ",x);
}
//Dijkstra+优先队列
const int Inf=0x3f3f3f3f;
int n,m,s,t,head[N],vis[N],cnt,dis[N],path[N];//path记录路径
struct EDGE{int v,w,next;}e[M*2];
struct node{
int v,d;
node(int vv,int dd):v(vv),d(dd){};
bool operator <(const node &o)const{
return d>o.d;
}
};//优先队列类型
priority_queue<node>q;
void Dijkstra(int s){
for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);//初始化
q.push(node(s,0));
while(!q.empty()){
node now=q.top();q.pop();
if(vis[now.v])continue;
vis[now.v]=1;
for(int j=head[now.v];j!=-1;j=e[j].next)
if(dis[e[j].v]>dis[now.v]+e[j].w){
dis[e[j].v]=dis[now.v]+e[j].w;
q.push(node(e[j].v,dis[e[j].v]));
//path[e[j].v]=now.v; //记录路径
}
}
}
void print_path(int x){//调用时参数为终点
if(path[x]==0){
printf("%d ",x);
return;
}
print_path(path[x]);
printf("%d ",x);
}
1. 时间复杂度比普通的Dijkstra和Ford低。期望值为O(k*e)
(其中k为所有顶点进队的平均次数,e是边的数量,可以证明k一般小于等于2)
2. 计算一个点到其他所有点的最短路,可以计算负权图,能够判断是否够有负环(存在点进队超过n次即存在负环)
//邻接表存图
const ll Inf=2147483647;
struct EDGE{int v,w,next;}e[M*2];
int n,m,vis[N],times[N],head[N],cnt,path[N];//times记录入队次数,path记录路径
ll dis[N];
int spfa(int s){//返回-1则有负环,0则没有
for(int i=1;i<=n;i++)dis[i]=(i==s?0:Inf);
vis[s]=1; //times[s]=1; //入队次数
queue<int>q;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=0; //释放
for(int i=head[now];i!=-1;i=e[i].next)
if(dis[e[i].v]>dis[now]+e[i].w){ //松弛
dis[e[i].v]=dis[now]+e[i].w;
//path[e[i].v]=now; //记录路径
if(!vis[e[i].v]){
q.push(e[i].v);
vis[e[i].v]=1;
//times[e[i].v]++;
//if(times[e[i].v]>n)return -1; //判负环
}
}
}
return 0;
}
void print_path(int x){//调用时参数为终点
if(path[x]==0){
printf("%d ",x);
return;
}
print_path(path[x]);
printf("%d ",x);
}
图论方法
1.正式比赛尽量用 dijkstra+优先队列,别用spfa
2.有向图求多点到单点的最短路径,让边反向即可。
3.最小生成森林,可以建立一个超级源点,连向所有的点(边权为建立点需要的代价)
二分图中:
匹配:给定一个二分图G=
,在G的一个子图M中,M的边集E中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
最大匹配:边数最多的一个匹配
完美匹配:也称为完备匹配,指一个图中所有的顶点都是匹配点的匹配。(完美匹配一定是最大匹配,但并非每个图都存在完美匹配.)
最优匹配:最优匹配又称为带权最大匹配,是指在带有权值边的二分图中,求一个匹配使得匹配边上的权值和最大。
最小顶点覆盖:最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联。--------------------- 【最小顶点覆盖数 = 最大匹配数】
最小路径覆盖:也称为最小边覆盖,指用最少的不相交简单路径覆盖二分图中的所有顶点。 ------------- 【最小路径覆盖数 = |V|-最大匹配数】
最大独立集:指寻找一个点集,使得其中任意两点在图中无连边。--------------------------------------- 【最大独立集 = |V|-最大匹配数】
增广路: 从一个未匹配点出发,依次交替经过非匹配边、匹配边、非匹配边…,到达另一个未匹配点的路径称为增广路。
[引用链接]
#include
using namespace std;
const int N=1007;
const int M=5e4+7;
struct Graph{
//匈牙利算法(二分图匹配) 时间复杂度O(nx*m+ny)
struct Edge{
int v,nxt;
}e[M*2];
int nx,ny,m,ans;//nx左部点数,ny右部点数,m边数,ans匹配数
int head[N],tot;
int dfn[N],match[N],tim;//tim是时间戳
void clear(){
memset(head,0,sizeof(head));
tim=tot=ans=0;
memset(match,0,sizeof(match));
memset(dfn,0,sizeof(dfn));
}
void add(int u,int v){e[++tot]=(Edge){v,head[u]};head[u]=tot;}
void addedge(int u,int v){add(u,v);add(v,u);}
bool dfs(int u,int ti){//ti是时间戳
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dfn[v]!=ti){//这一轮还没有遍历到点v
dfn[v]=ti;
if(match[v]==0||dfs(match[v],ti)){//点v还没有被匹配 或者是 找到了一条可增广的路径
match[v]=u;
return 1;
}
}
}
return 0;
}
void solve(){//匹配
for(int i=1;i<=nx;i++)
if(dfs(i,++tim))ans++;
}
}G;
int main(){
G.clear();
cin>>G.nx>>G.ny>>G.m;
for(int i=1,u,v;i<=G.m;i++){
scanf("%d%d",&u,&v);
G.addedge(u,G.nx+v);
}
G.solve();
cout<<G.ans;
}
例题:hdu2255
#include
using namespace std;
const int N=307;
const int M=N*N;
const int Inf=0x3f3f3f3f;
struct Graph{
//KM算法[优化](带权二分图最佳匹配) 时间复杂度O(n^3)
int match[N],lx[N],ly[N],slack[N],fa[N];
int e[N][N];//邻接矩阵存图
bool visx[N],visy[N];
int n,nx,ny,ans;//n总点数,nx左部点数,ny右部点数,ans最佳匹配总权值
void clear(){
memset(e,0,sizeof(e));
memset(match,-1,sizeof(match));
for(int i=0;i<N;i++)
lx[i]=ly[i]=slack[i]=fa[i]=0;
}
int findpath(int x){
int tempDelta;
visx[x]=true;
for(int y=1;y<=ny;y++){
if(visy[y])continue;
tempDelta =lx[x]+ly[y]-e[x][y];
if(tempDelta==0){
visy[y]=true;
fa[y+nx]=x;
if(match[y]==-1)return y+nx;
fa[match[y]]=y+nx;//记录交替树的父亲信息(为了区别X,Y集合,Y的点都映射成n+y)
int res=findpath(match[y]);
if(res>0)return res;//返回增广路的末端叶子节点
}else
if(slack[x]>tempDelta)//统计以x为准的slack值。
slack[x]=tempDelta;
}
return -1;
}
void KM(){
for(int x=1;x<=nx;++x){
for(int i=1;i<=nx;++i)slack[i]=Inf;
for(int i=1;i<=nx+ny;i++)fa[i]=-1;
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));//换到外面,可以保留原树
int fir=1;int leaf=-1;
while(true){
if(fir==1){
leaf=findpath(x);
fir=0;
}else{
for(int i=1;i<=nx;i++){
if(slack[i]==0){//只接着搜有新边加入的X点
slack[i]=Inf;//slack要重新清空,方以后接着用
leaf=findpath(i);
if(leaf>0)break;
}
}
}
if(leaf>0){
int p=leaf;
while(p>0){
match[p-nx]=fa[p];
p=fa[fa[p]];//顺着记录一路找找上去
}
break;
}else{
int delta=Inf;
for(int i=1;i<=nx;++i)
if(visx[i]&&delta>slack[i])
delta=slack[i];
for(int i=1;i<=nx;++i)
if(visx[i]){lx[i]-=delta;slack[i]-=delta;}//X点的slack要响应改变,slack变0说明有新边加入
for(int j=1;j<=ny;++j){
if(visy[j])
ly[j]+=delta;
}
}
}
}
}
void solve(){
for(int i=1;i<=nx;++i){
lx[i]=-Inf;
for(int j=1;j<=ny;++j)
if(lx[i]<e[i][j])lx[i]=e[i][j];
}
KM();
ans=0;
for(int i=1;i<=ny;++i)
if(match[i]!=-1)ans+=e[match[i]][i];
}
}G;
int main(){
int n;
while(cin>>n){
G.clear();
G.nx=G.ny=n;G.n=2*n;
for(int i=1;i<=G.nx;i++)
for(int j=1;j<=G.ny;j++)
scanf("%d",&G.e[i][j]);
G.solve();
cout<<G.ans<<"\n";
}
}
#include
using namespace std;
const int N=1007;
const int M=N*N*2;
struct Tree_With_Flower{//Tree with flower 时间复杂度O(n^3)
struct Edge{
int v,nxt;
}e[M];
int head[N],tot;
inline void add(int u,int v){e[++tot]=(Edge){v,head[u]};head[u]=tot;}
inline void addedge(int u,int v){add(u,v);add(v,u);}
int q[M],ql,qr;
int n,m,ans,tim,pre[N];//n点数,m边数,ans匹配数
int dfn[N],match[N],cl[N],fa[N];
inline void clear(){
memset(head,0,sizeof(head));
memset(match,0,sizeof(match));
memset(dfn,0,sizeof(dfn));
tot=ans=tim=0;
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline int lca(int x,int y){
for(++tim;;swap(x,y))if(x){
x=find(x);
if(dfn[x]==tim)return x;
else dfn[x]=tim,x=pre[match[x]];
}
}
inline void shrink(int x,int y,int p){
while(find(x)!=p){
pre[x]=y,y=match[x];
if(cl[y]==2)cl[y]=1,q[++qr]=y;
if(find(x)==x)fa[x]=p;
if(find(y)==y)fa[y]=p;
x=pre[y];
}
}
inline bool aug(int s){
for(register int i=1;i<=n;++i)fa[i]=i;
memset(cl,0,sizeof(cl));memset(pre,0,sizeof(pre));
cl[q[ql=qr=1]=s]=1;
while(ql<=qr){
int u=q[ql++];
for(register int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v){
if(cl[v]==2||find(v)==find(u))continue;
if(!cl[v]){
cl[v]=2;pre[v]=u;
if(!match[v]){
for(register int x=v,las,y;x;x=las)
las=match[y=pre[x]],match[x]=y,match[y]=x;
return 1;
}
cl[match[v]]=1,q[++qr]=match[v];
}
else if(cl[v]==1){
int l=lca(u,v);
shrink(u,v,l);
shrink(v,u,l);
}
}
}
return 0;
}
void solve(){//匹配
for(int i=1;i<=n;i++)ans+=(!match[i]&&aug(i));
}
}T;
int main(){
T.clear();
cin>>T.n>>T.m;
for(int i=1,u,v;i<=T.m;i++){
scanf("%d%d",&u,&v);
T.addedge(u,v);
}
T.solve();
cout<<T.ans<<"\n";
for(int i=1;i<=T.n;i++)printf("%d ",T.match[i]);
return 0;
}