NOIp 图论基础

从最害怕的专题到最喜欢的专题……嗯果然多刷题是最有用的w

目录

  • 存图
    • 邻接矩阵
    • 链式前向星
    • vector
  • 最短路
    • SPFA(死了)
    • Floyed
    • 堆优化Dijkstra
    • 最短路计数
    • 严格次短路
    • 分层图最短路
  • 最小生成树
    • Kruskal
    • Prim
  • Tarjan
    • 强连通分量
    • 缩点
    • 割点
    • 离线LCA
  • 倍增LCA
  • 拓扑排序
  • 树的直径
    • dfs法
    • dp法
  • 差分约束
  • 网络流
    • 二分图匹配
    • 最大流
    • 费用流
  • 负环
  • 最小环
  • 欧拉回路

一、存图

1、邻接矩阵存图

memset(mapp,128/3,sizeof mapp);
	for(int i=1;i<=n;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		mapp[u][v]=mapp[v][u]=min(mapp[u][v],w);
	}

2、链式前向星

struct sth{
	int to,nxt,dis;
};
int tot,head[MAXN];
void add(int u,int v,int w){
	a[++tot].to=v;a[tot].nxt=head[u];
	a[tot].dis=w;head[u]=tot;
}

3、vector

vector<int>P[MAXN];
for(int i=1;i<=n;i++){
	int u,v;
	P[u].push_back(v);
	P[v]=push_back(u);
}

以下内容均省去建图过程,一般是前向星存的。

二、最短路

1、SPFA (它曾活过啊)

void SPFA(){
	queue<int>q;	
	memset(d,0x7f7f7f,sizeof d);
	q.push(s);d[s]=0;vis[s]=1;
	while(!q.empty()){
		int noww=q.front();q.pop();
		for(int j=head[noww];j;j=a[j].nxt){
			int vv=a[j].to;
			if(d[vv]>d[noww]+a[j].dis){
				d[vv]=d[noww]+a[j].dis;
				if(!vis[vv])q.push(vv),vis[vv]=1;
			}
		}vis[noww]=0;
	}
}

2、Floyed

void Floyed(){
	for(int k=1;k<=n;k++)
	  for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++){
	      if(mapp[i][j]>mapp[i][k]+mapp[k][j])
	        mapp[i][j]=mapp[i][k]+mapp[k][j];
		}
}

3、堆优化Dijkstra

struct node{
    int to,dis;
    bool operator <(const node &t) const{
        return dis>t.dis;
    }
}a[MAXM<<1];
priority_queue<node>q;
void dij(){
	for(int i=1;i<=m;i++)d[i]=2147483647;
	d[s]=0;node qwq;qwq.to=s;qwq.dis=0;
    q.push(qwq);
    while(!q.empty()){
        node P=q.top();q.pop();
        int noww=P.to;
        if(visit[noww])continue;
        visit[noww]=1;
        for(int j=head[noww];j;j=nxt[j]){
            int vv=a[j].to;
            if(d[vv]>d[noww]+a[j].dis){
			    d[vv]=d[noww]+a[j].dis;
                node qwq;qwq.to=vv;qwq.dis=d[vv];
                q.push(qwq);
            }
        }
    }
}

四、最短路计数

void SPFA(){
    q.push(1);dis[1]=0;
	t[1]=1;vis[1]=1;
    while(!q.empty()){
        int noww=q.front();q.pop();
        if(noww==n)continue;//重要 
        for(int i=1;i<=n;i++){
            if(dis[noww]+a[noww][i]==dis[i])
            t[i]+=t[noww];
            if(dis[noww]+a[noww][i]<dis[i]){
                dis[i]=dis[noww]+a[noww][i];
                t[i]=t[noww];
            }
		    if(t[i]&&!vis[i])vis[i]=1,q.push(i);
        }
		t[noww]=0,vis[noww]=0;//重要 
    }
}

五、严格次短路

priority_queue<P,vector<P>,greater<P> >q;
void dij(){
    for(int i=1;i<=n;i++)d1[i]=d2[i]=inf;
    d1[1]=0;
    q.push(P(1,0));
    while(!q.empty()){
        int noww=q.top().first;
        int d=q.top().second;q.pop();
        for(int j=head[noww];j;j=a[j].nxt){
            int vv=a[j].to;int disv=a[j].dis+d;
            if(disv<d1[vv]){
                swap(d1[vv],disv);//重要 
                q.push(P(vv,d1[vv]));
            }
            if(disv>d1[vv]&&disv<d2[vv]){
                d2[vv]=disv;
                q.push(P(vv,d2[vv]));
            }
        }
    }
}

六、分层图最短路

了解了算法就一眼能瞪出正解的题型...

例题

建一个k+1层的图,层内连边,层之间连0边即可。(其实是dp思想)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=200005;
const int MAXM=6000005;
int n,m,k,tot,head[MAXN],s,t;
int d[MAXN];
struct sth{
	int to,nxt,dis;
}a[MAXM];
struct node{
	int qwq,diss;
	bool operator <(const node& t)const{
	    return diss>t.diss;
	}
}; 
priority_queue<node>q;
void add(int u,int v,int w){
	a[++tot].dis=w;a[tot].to=v;
	a[tot].nxt=head[u];head[u]=tot;
}
void dij(){
	memset(d,128/3,sizeof d);d[s]=0;
	node ss;ss.diss=0;ss.qwq=s;q.push(ss);
	while(!q.empty()){
		node noww=q.top();q.pop();
		int nowq=noww.qwq;
		for(int j=head[nowq];j;j=a[j].nxt){
			int vv=a[j].to;
			if(d[vv]>d[nowq]+a[j].dis){
				d[vv]=d[nowq]+a[j].dis;
				node orz;orz.qwq=vv;orz.diss=d[vv];
				q.push(orz);
			}
		}
	}
	
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	scanf("%d%d",&s,&t);s++;t++;
	for(int i=1;i<=m;i++){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		u++;v++;
		for(int j=0;j<=k;j++){
			add(u+j*n,v+j*n,w);add(v+j*n,u+j*n,w);
			if(j!=k){
				add(u+j*n,v+(j+1)*n,0);
				add(v+j*n,u+(j+1)*n,0);
			}
		}
	}
	for(int j=0;j<k;j++){
		add(t+n*j,t+n*(j+1),0);
		add(t+n*(j+1),t+n*j,0);
	}
	dij();
	printf("%d
",d[t]);
	return 0;
}

七、最小生成树

1、适合稀疏图的Kruskal

void qsort(int l,int r){
    int i=l,j=r;node mid=a[(i+j)/2];
    while(i<=j){
        while(cmp(a[i],mid))i++;
        while(cmp(mid,a[j]))j--;
        if(i<=j){
            t=a[i];a[i]=a[j];a[j]=t;
            i++;j--;
        }
    }
    if(i<r)qsort(i,r);
    if(l<j)qsort(l,j);
}
int findx(int x){return (x==fa[x])?fa[x]:fa[x]=findx(fa[x]);}
void solve(){
    qsort(1,m);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        if(findx(a[i].x)!=findx(a[i].y)){
            ans+=a[i].w;
            fa[findx(a[i].x)]=a[i].y;
            p++;
            if(p==n)return ;
        }
    }
}

2、适合稠密图的Prim

(都8012年了我才会Prim)

void Prim(){
	for(int i=1;i<=n;i++){
		mincost[i]=mapp[1][i];
	}
	use[1]=1;//重要 
	for(int i=1;i<n;i++){
		int minn=99999999;
		int j;
		for(int k=1;k<=n;k++){
			if(!use[k]&&mincost[k]<minn){
				j=k;
				minn=mincost[k];
			}
		}
		use[j]=1;
		ans+=minn;
		for(int k=2;k<=n;k++){
			if(!use[k]&&mincost[k]>mapp[j][k]){
				mincost[k]=mapp[j][k];
			}
		}
	}
}

八、Tarjan

1、强联通分量

int dfs(int x){
	dfn[x]=low[x]=++cntt;
	vis[x]=1;s.push(x);
	for(int j=head[x];j;j=a[j].nxt){
		int vv=a[j].to;
		if(!dfn[vv]){
			int lowv=dfs(vv);
			low[x]=min(low[x],lowv);
		}
		else{
			if(vis[vv]){
				low[x]=min(low[x],low[vv]); 
			}
		}
	}
	if(dfn[x]==low[x]){
		cntcol++;colour[x]=cntcol;cnt[cntcol]++;
		vis[x]=0;
		sta[cntcol][1]=x;
		while(s.top()!=x){
			colour[s.top()]=cntcol;cnt[cntcol]++;
			vis[s.top()]=0;sta[cntcol][cnt[cntcol]]=s.top();
			s.pop();
		}s.pop();
		maxcnt=max(maxcnt,cnt[cntcol]);
	}
	return low[x];
}
void tarjan(){
	for(int i=1;i<=n;i++){
		if(!dfn[i])dfs(i);
	}
}

2、缩点

int tarjan(int u){
    dep++;vis[u]=1;
    s.push(u);
    int lowu=dep;dfn[u]=low[u]=dep;
    for(int j=head[u];j;j=a[j].nxt){
        int vv=a[j].to;
        if(!dfn[vv]){
            int lowv=tarjan(vv);
            lowu=min(lowu,lowv);
        }else{
            if(vis[vv])
            lowu=min(lowu,low[vv]);
        }
    }
    low[u]=lowu;
    if(low[u]==dfn[u]){
        cnt++;
        colour[u]=cnt;vis[u]=0;
        newq[cnt]+=q[u];
        while(s.top()!=u){
            colour[s.top()]=cnt;
            newq[cnt]+=q[s.top()];
            vis[s.top()]=0;
            s.pop();
        }s.pop();
    }
    return lowu;
}
void work(){
    for(int i=1;i<=n;i++){
        if(!dfn[i])tarjan(i);
    }
    for(int i=1;i<=m;i++)
        if(colour[x[i]]!=colour[y[i]])add(colour[x[i]],colour[y[i]]);
}

3、割点

void tarjan(int u,int fa){
	int ch=0;dep++;
	dfn[u]=low[u]=dep;
	for(int j=head[u];j;j=a[j].nxt){
		int vv=a[j].to;
		if(!dfn[vv]){
			ch++;
			tarjan(vv,u);
			low[u]=min(low[u],low[vv]);
			if(low[vv]>=dfn[u])iscut[u]=1;
		}else{
			if(vv!=fa&&dfn[vv]<dfn[u])
			low[u]=min(low[u],low[vv]);
		}
	}
	if(fa==-1&&ch==1)iscut[u]=0;
	if(fa==-1&&ch>=2)iscut[u]=1;
}
void work(){
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i,-1);
	}
}

4、桥

void tarjan(int u,int fa){
	int ch=0;dep++;
	dfn[u]=low[u]=dep;
	for(int j=head[u];j;j=a[j].nxt){
		int vv=a[j].to;
		if(!dfn[vv]){
			ch++;
			tarjan(vv,u);
			low[u]=min(low[u],low[vv]);
			if(dfn[u]<low[vv])printf("%d %d
",u,vv); 
		}else{
			if(vv!=fa&&dfn[vv]<dfn[u])
			low[u]=min(low[u],low[vv]);
		}
	}
}
void work(){
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i,-1);
	}
}

5、离线求LCA

#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
int n,m,root;
struct sth{
	int nxt,to,lca;
}a[MAXN],b[MAXN];
int tot,toot,head[MAXN],head2[MAXN];
void add(int u,int v){
	a[++tot].to=v;a[tot].nxt=head[u];
	head[u]=tot;
}
void add2(int u,int v){
	b[++toot].to=v;b[toot].nxt=head2[u];
	head2[u]=toot;
}
bool vis[MAXN];
int fa[MAXN];
int find(int x){return x==fa[x]?fa[x]:fa[x]=find(fa[x]);}
void dfs(int x){
	fa[x]=x;
	vis[x]=1;
	for(int j=head[x];j;j=a[j].nxt){
		int vv=a[j].to;
		if(!vis[vv]){
			dfs(vv);
			fa[vv]=x;
		}
	}
	for(int j=head2[x];j;j=b[j].nxt){
		int vv=b[j].to;
		if(vis[vv]){
			b[j].lca=find(vv);
			if(j&1)
			   b[j+1].lca=b[j].lca;
			else b[j-1].lca=b[j].lca;
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&root);
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add2(u,v);add2(v,u);
	}
	dfs(root);
	for(int i=1;i<=toot;i+=2)printf("%d
",b[i].lca);
	return 0;
}

九、倍增求LCA

void dfs(int u,int f,int d){
	fa[u][0]=f;deep[u]=d;
	for(int i=head[u];i;i=a[i].nxt){
		int vv=a[i].to;
		if(vv!=f)dfs(vv,u,d+1);
	}
}
int lca(int u,int v){
	if(deep[u]>deep[v])swap(u,v);
	for(int k=0;k<19;k++){
		if(((deep[v]-deep[u])>>k)&1)v=fa[v][k];
	}if(u==v)return u;
	for(int k=18;k>=0;k--){
		if(fa[v][k]!=fa[u][k]){
			u=fa[u][k];v=fa[v][k];
		}
	}return fa[u][0];
}
void work(){
	dfs(root,-1,0);
	for(int k=0;k+1<19;k++){
		for(int j=1;j<=n;j++)
		if(fa[j][k]<0)fa[j][k+1]=-1;
		else fa[j][k+1]=fa[fa[j][k]][k];	
	}
} 

十、拓扑排序

void tuopu(){//不断找入度为0的点
	for(int i=1;i<=n;i++)if(!rudu[i])q.push(i);
	while(!q.empty()){
		int noww=q.front();q.pop();
		tp[++cnt]=noww;
		for(int j=head[noww];j;j=a[j].nxt){
			int vv=a[j].to;rudu[vv]--;
			if(!rudu[vv])q.push(vv);
		}
	}if(cnt!=n){
		printf("No Solution!
");return;
	}
	for(int i=1;i<=cnt;i++)printf("%d ",tp[i]);
	printf("
");
}

十一、树的直径

“你知道吗,树的直径有两种求法。”
(好像还可以bfs求,然而我没写过)

1、两遍dfs求法

void dfs(int noww){
	if(dis[noww]>maxn){
		maxn=dis[noww];
		t=noww;
	}
    vis[noww]=1;
    for(int j=head[noww];j;j=a[j].nxt){
        int vv=a[j].to;
        if(!vis[vv]){
            dis[vv]=dis[noww]+a[j].to;
            dfs(vv);
        }
    }
}
void work(){
	maxn=-99999999;
	memset(vis,0,sizeof vis);
	memset(dis,128/3,sizeof dis);
	dis[1]=0;
	dfs(1);
	s=t;
	maxn=-99999999;
	memset(vis,0,sizeof vis);
	memset(dis,128/3,sizeof dis);
	dis[s]=0;
	dfs(s);
}

2、dp求法

void dfs(int noww,int from) {
    dp[noww][0]=0;dp[noww][1]=0;
    for (int j=head[noww];j;j=a[j].nxt){
        int vv=a[j].to;
        if(vv!=from){
            dfs(vv,i);
            if (dp[vv][0]+a[j].dis>dp[i][0]){
                dp[i][1]=dp[i][0];
                dp[i][0]=dp[vv][0]+a[j].dis;
            }
            else if(dp[to[j]][0]+a[j].dis>dp[i][1])
                    dp[i][1]=dp[vv][0]+a[j].dis;
        }
    }
    ans=max(ans,dp[i][0]+dp[i][1]);
}//dfs(1,-1);

十二、差分约束

差分约束并没有固定的模板,但有固定的套路——

将条件中的差值不等式转换成最短路松弛时的不等式即可。

以洛谷P3275糖果一题为例,将五种要求转化成连边,跑SPFA。

不能成立的条件是出现环。

这里给出建图和最短路部分

bool SPFA(){
    vis[0]=1,q.push(0);
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=0;
        if(tot[u]==n-1){cout<<-1;return 0;}
        tot[u]++;
        for(int i=head[u];i;i=a[i].nxt)
            if(d[a[i].to]<d[u]+a[i].dis){
                d[a[i].to]=d[u]+a[i].dis;
                if(!vis[a[i].to])vis[a[i].to]=1,q.push(a[i].to);
            }
    }return 1;
}
void init(){
	for(ll i=1;i<=k;i++){
        ll opt,u,v;
        cin>>opt>>u>>v;
        if(opt==1){
            add(u,v,0);add(v,u,0);
        }
        if(opt==2){
            add(u,v,1);
            if(u==v){
                cout<<"-1";return 0;
            }
        }
        if(opt==3){
            add(v,u,0);
        }
        if(opt==4){
            add(v,u,1);if(u==v){
                cout<<"-1";return 0;
            }
        }
        if(opt==5){
            add(u,v,0);
        }
    }
    for(ll i=n;i>=1;i--)add(0,i,1);
}

十三、网络流

1、二分图匹配

当然可以跑网络流,然而二分图匹配更好写。(算法名字我分不清....)

bool dfs(int x){
	for(int j=1;j<=m;j++){
		if(!mapp[x][j])continue;
		if(use[j])continue;
		use[j]=1;
		if(!match[j]||dfs(match[j])){
			match[j]=x;
			return 1;
		}
	}
	return 0;
}
void work(){
	for(int i=1;i<=n;i++){
	    memset(use,0,sizeof use);
		if(dfs(i))ans++;
	}
}

2、最大流

int bfs(){
    memset(deep,0,sizeof(deep));
    queue<int>q;
    deep[s]=1;
    q.push(s);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=e[i].nxt){
            if(e[i].dis&&!deep[e[i].to]){
                deep[e[i].to]=deep[u]+1;
                q.push(e[i].to);
            }
        }
    }
    if(deep[t])return 1;
    return 0;
}
int dfs(int u,int d) {
    if(u==t)return d;
    for(int i=head[u];i!=-1;i=e[i].nxt){
        if((deep[e[i].to]==deep[u]+1)&&(e[i].dis)){
            int di=dfs(e[i].to,min(d,e[i].dis)); 
            if(di){
                e[i].dis-=di;
                e[i^1].dis+=di;
                return di;
            }
        }
    }
    return 0;
}
int dinic(){
    int ret=0;
    while(bfs())
        while(int di=dfs(s,INF)) 
            ret+=di;
    return ret;
}

还有最大流最小割定理。我不会证也懒得copy了...

3、费用流

把最大流的bfs改成SPFA就好啦

int spfa(){
    memset(d,0x7f,sizeof(d));
    d[s]=0;vis[s]=1;q.push(s);
    while(!q.empty()){
    	int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=a[i].nxt){
            int vv=a[i].to;
            if(a[i].dis&&d[u]+a[i].cost<d[vv]){
                d[vv]=d[u]+a[i].cost;
                if(!vis[vv])vis[vv]=1,q.push(vv);
            }
        }
    }
    return d[t];
}
int dfs(const int u,const int f){
    if(f==0||u==t)return f;
    int ret=0;vis[u]=1;
    for(int i=cur[u];i;i=a[i].nxt){
        cur[u]=i;
        int v=a[i].to;
        if(d[v]!=d[u]+a[i].cost||!a[i].dis)continue;
        if(!vis[v]){
            int ans=dfs(v,min(f-ret,a[i].dis));
            ret+=ans;
			a[i].dis-=ans,a[i^1].dis+=ans;
            mincost+=a[i].cost*ans;
        }
    }
    vis[u]=0;
    return ret;
}
void dinic(){
    while(spfa()!=0x7f7f7f7f){
        for(int i=1;i<=n;i++)cur[i]=head[i];
        while(int x=dfs(s,0x7f7f7f7f)){
            maxflow+=x;
            if(mincost>=0x7f7f7f2b)return;
        }
    }
}

十四、负环

如果有一个点入队次数大于(n),那么一定有负环

bool SPFA(int s){
	queue<int>q;
	memset(vis,0,sizeof vis);
	memset(d,128/3,sizeof d);
	d[s]=0;vis[s]=1;cnt[s]=1;q.push(s);
	while(!q.empty()){
		int noww=q.front();q.pop();vis[noww]=0;
		for(int j=head[noww];j;j=a[j].nxt){
		int vv=a[j].to;
		if(d[vv]>d[noww]+a[j].dis){
			d[vv]=d[noww]+a[j].dis;
			cnt[vv]=cnt[noww]+1;
			if(cnt[vv]>n)return 0;
			if(!vis[vv])vis[vv]=1,q.push(vv);	
		}
		}
	}return 1;
}

十五、最小环

只见过一道求最小环的题,感觉很有意思,值得一做。(虽然这道题的精髓在于建图)

for(int k=1;k<=a;k++){
	for(int i=1;i<=k-1;i++)
		for(int j=i+1;j<=k-1;j++){
		  	int tmp=qwq[i][j]+dis[i][k]+dis[k][j];
		  	if(tmp<ans)ans=tmp;	
		    }		  
	for(int i=1;i<=a;i++)
		for(int j=1;j<=a;j++){
		    if(qwq[i][j]>qwq[i][k]+qwq[k][j])
		    qwq[i][j]=qwq[i][k]+qwq[k][j];
	    }
}

正确性证明:在枚举环上的(ij)时,因为这两个点没有被(k)松弛过,所以必定不会经过(k)

十六、欧拉回路

void dfs(int x){
    for (int i=1;i<=n;i++)
    if (mapp[x][i]){
        mapp[x][i]--;
		mapp[i][x]--;
        dfs(i);
    }
	S.push(x);
}
void work(){
	for(int i=1;i<=m;i++){
        int u,v;scanf("%d%d",&u,&v);
        mapp[u][v]++;
		mapp[v][u]++;
        b[u]++;b[v]++;
        n=max(n,max(u,v));
    }
    s=1;int cnt=0;
    for(int i=n;i>=1;i--)
	if(b[i]&1)s=i,cnt++;
    if(cnt&&cnt!=2){
        printf("No Solution!
");
        exit(0);
	}
	dfs(s);
} 


原文地址:https://www.cnblogs.com/erutsiom/p/9923640.html