图论二

复健Day7

图论二

1.拓扑排序

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define maxn 10010
using namespace std;

int head[maxn],tot,n;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int in[maxn];

void topu()
{
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(!in[i]) q.push(i);
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		printf("%d ",u);
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			in[v]--;
			if(!in[v]) q.push(v);
		}
	}
}

int main()
{
	cin>>n;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)
	{
		int v;
		while(cin>>v,v) add(i,v),in[v]++;
	}
	topu();
	return 0;
}

求字典序最小的拓扑序,我们把queue改为priority_queue就可以了

2.最短路问题

Dijkstra模板(不能处理有负边权的情况)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int maxn=2e5+10;

int head[maxn],tot;
int s;
int dis[maxn],vis[maxn];

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

struct Node
{
	int u,dis;
	Node(){}
	Node(int u,int dis):u(u),dis(dis){}
	bool operator < (const Node &rhs) const {
		return dis>rhs.dis;
	}
};

void Dijkstra()
{
	memset(dis,0x3f,sizeof(dis));
	priority_queue<Node> q;
	q.push(Node(s,0));
	dis[s]=0;
	while(!q.empty())
	{
		Node x=q.top();
		q.pop();
		int u=x.u;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(dis[v]>dis[u]+ed[i].dis)
			{
				dis[v]=dis[u]+ed[i].dis;
				q.push(Node(v,dis[v]));
			}
		}
	}
}

int main()
{
	int n,m;
	cin>>n>>m>>s;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
	}
	Dijkstra();
	for(int i=1;i<=n;i++) printf("%d ",dis[i]);
	printf("\n");
	return 0;
}

SPFA算法(不能处理含负环的情况)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int maxn=1e6+10;

int head[maxn],tot;
int s,n,m;
int dis[maxn],vis[maxn];

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

void SPFA()
{
	for(int i=1;i<=n;i++) dis[i]=2147483647;
	queue<int> q;
	q.push(s);
	dis[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(dis[v]>dis[u]+ed[i].dis)
			{
				dis[v]=dis[u]+ed[i].dis;
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
}

int main()
{
	cin>>n>>m>>s;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
	}
	SPFA();
	for(int i=1;i<=n;i++) printf("%d ",dis[i]);
	printf("\n");
	return 0;
}

全源最短路算法Johnson算法(可处理带负边权的情况)

nDijkstra算法的基础上稍加改造,使其能够处理负边权,即建立虚拟源点

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;

const int maxn=6e3+10;
const int inf=1e9;

int head[maxn],tot;
int h[maxn],cnt[maxn];
int s,n,m;
int dis[maxn],vis[maxn];

struct Edge
{
	int v,dis,nxt;
	Edge(){}
	Edge(int v,int dis,int nxt):v(v),dis(dis),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

bool SPFA(int s)
{
	memset(h,63,sizeof(h));
	queue<int> q;
	q.push(s);
	h[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(h[v]>h[u]+ed[i].dis)
			{
				h[v]=h[u]+ed[i].dis;
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
					cnt[v]++;
					if(cnt[v]==n+1) return false;//如果一个点入队超过m次,则有负环
				}
			}
		}
	}
	return true;
}

struct Node
{
	int u,dis;
	Node(){}
	Node(int u,int dis):u(u),dis(dis){}
	bool operator < (const Node &rhs) const{
		return dis>rhs.dis;
	}
};

void Dijkstra(int s)
{
	priority_queue<Node> q;
	for(int i=1;i<=n;i++) dis[i]=inf;
	memset(vis,0,sizeof(vis));
	dis[s]=0;
	q.push(Node(s,0));
	while(!q.empty())
	{
		Node x=q.top();
		q.pop();
		int u=x.u;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(dis[v]>dis[u]+ed[i].dis)
			{
				dis[v]=dis[u]+ed[i].dis;
				if(!vis[v]) q.push(Node(v,dis[v]));
			}
		}
	}
}

signed main()
{
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
	}
	for(int i=1;i<=n;i++) add(0,i,0);
	if(!SPFA(0))
	{
		printf("-1\n");
		return 0;
	}
	for(int u=1;u<=n;u++)
	{
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			ed[i].dis+=h[u]-h[ed[i].v];
		}
	}
	for(int i=1;i<=n;i++)
	{
		Dijkstra(i);
		int ans=0;
		for(int j=1;j<=n;j++)
		{
			if(dis[j]==inf)
			{
				ans+=j*inf;
			}
			else ans+=j*(dis[j]+h[j]-h[i]);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

3.无向图的最小环问题

https://www.luogu.com.cn/problem/P6175

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;

int d[maxn][maxn],w[maxn][maxn];
int n,m;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i!=j) w[i][j]=1e8;
		}
	}
	for(int i=1;i<=m;i++)
	{
		int u,v,W;
		cin>>u>>v>>W;
		w[u][v]=w[v][u]=W;
	}
	memcpy(d,w,sizeof(d));
	int ans=1e8;
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<k;i++)
		{
			for(int j=i+1;j<k;j++)
			{
				ans=min(ans,d[i][j]+w[j][k]+w[k][i]);
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
		}
	}
	if(ans==1e8) printf("No solution.\n");
	else printf("%d\n",ans);
	return 0;
}

4.最近公共祖先(LCA)

倍增算法,复杂度为O((n+m)logn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=5e5+10;

int head[maxn],tot;
struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int dep[maxn],fa[maxn][20];

void dfs(int u,int f)
{
	dep[u]=dep[f]+1;
	fa[u][0]=f;
	for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v!=f) dfs(v,u);
	}
}

int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
	{
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
	}
	if(u==v) return v;
	for(int i=19;i>=0;i--)
	{
		if(fa[u][i]!=fa[v][i])
		{
			u=fa[u][i],v=fa[v][i];
		}
	}
	return fa[u][0];
}

int main()
{
	int n,m,s;
	cin>>n>>m>>s;
	memset(head,-1,sizeof(head));
	add(0,s);add(s,0);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	dfs(s,0);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		printf("%d\n",lca(x,y));
	}
	return 0;
}

Tarjan算法(离线算法),时间复杂度O((n+m))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=1e6+10;

vector<pair<int,int> > query[maxn];

int head[maxn],tot;
struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int dep[maxn],fa[maxn],vis[maxn],ans[maxn];

int find(int u)
{
	if(u==fa[u]) return u;
	return fa[u]=find(fa[u]);
}

void tarjan(int u)
{
	vis[u]=1;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!vis[v])
		{
			tarjan(v);
			fa[v]=u;//回u时,把v指向u
		}
	}
	for(int i=0;i<query[u].size();i++)
	{
		int x=query[u][i].first,id=query[u][i].second;
		if(vis[x]) ans[id]=find(x);
	}
}

int main()
{
	int n,m,s;
	cin>>n>>m>>s;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		query[x].push_back({y,i});
		query[y].push_back({x,i});//表示第i次查询
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	tarjan(s);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

5.树上修改与查询(树链剖分)

fa[u]:存u的父节点

dep[u]:存u的深度

son[u]:存u的重儿子

sz[u]:存以u为根的子树的个数

top[u]:存以u所在重链的顶点

id[u]:存u剖分后的新编号

nw[cnt]:存新编号在树中所对应节点的权值

树链剖分的模板

void dfs(int u,int f)//处理fa,dep,sz,son
{
	fa[u]=f,dep[u]=dep[f]+1,sz[u]=1;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==f) continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v]) son[u]=v;
	}
}

void dfs2(int u,int t)//处理top,id,nw
{
	top[u]=t,id[u]=++cnt,nw[cnt]=w[u];
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];~i;i=ed[i].v)
	{
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}

将树链剖分剖成的新序列用线段树去维护

我们可以查询树从xy结点最短路径上所有节点的值之和

LL query_path(int u,int v)
{
	LL res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=query(1,id[top[u]],id[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res+=query(1,id[v],id[u]);
	return res;
}

https://www.luogu.com.cn/problem/P3384

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
#define int long long
using namespace std;

const int maxn=1e6+10;
int w[maxn],nw[maxn],fa[maxn],top[maxn],dep[maxn],sz[maxn],id[maxn];
int son[maxn],cnt;
int n,m,r,p;

struct Node
{
	int l,r;
	LL add,sum;
}tree[maxn];

void pushup(int rt)
{
	tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum;
	tree[rt].sum%=p;
}

inline void pushdown(int rt)
{
	if(tree[rt].add)
	{
		tree[rt<<1].add+=tree[rt].add;
		tree[rt<<1|1].add+=tree[rt].add;
		tree[rt<<1].sum+=tree[rt].add%p*(tree[rt<<1].r-tree[rt<<1].l+1)%p;
		tree[rt<<1|1].sum+=tree[rt].add%p*(tree[rt<<1|1].r-tree[rt<<1|1].l+1)%p;
		tree[rt].add=0;
	}
}

inline void build(int rt,int l,int r)
{
	tree[rt].l=l,tree[rt].r=r,tree[rt].sum=nw[l];
	tree[rt].add=0; 
	if(l==r) return;
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	pushup(rt);
}

inline void update(int rt,int l,int r,int k)
{
	if(l<=tree[rt].l&&r>=tree[rt].r)
	{
		tree[rt].add+=k;
		tree[rt].sum+=k%p*(tree[rt].r-tree[rt].l+1)%p;
		return;
	}
	pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(l<=mid) update(rt<<1,l,r,k);
	if(r>mid) update(rt<<1|1,l,r,k);
	pushup(rt);
}

inline void update_path(int u,int v,int k)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u],k);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	update(1,id[v],id[u],k);
}

inline void update_tree(int rt,int k)
{
	update(1,id[rt],id[rt]+sz[rt]-1,k);
}

inline LL query(int rt,int l,int r)
{
	if(l<=tree[rt].l&&r>=tree[rt].r) return tree[rt].sum;
	pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	LL res=0;
	if(l<=mid) res+=query(rt<<1,l,r)%p;
	if(r>mid) res+=query(rt<<1|1,l,r)%p;
	return res%p;
}

inline LL query_path(int u,int v)
{
	LL res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<=dep[top[v]]) swap(u,v);
		res+=query(1,id[top[u]],id[u])%p;
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res+=query(1,id[v],id[u])%p;
	return res%p;
}

inline LL query_tree(int rt)
{
	return query(1,id[rt],id[rt]+sz[rt]-1);
}

int head[maxn],tot;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

inline void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

inline void dfs1(int u,int f)
{
	fa[u]=f,dep[u]=dep[f]+1,sz[u]=1;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==f) continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v]) son[u]=v;
	}
}

inline void dfs2(int u,int t)
{
	top[u]=t,id[u]=++cnt,nw[cnt]=w[u];
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}

int read()
{
	int ans=0,f=1;char i=getchar();
	while(i<'0'||i>'9'){if(i=='-') f=-1;i=getchar();}
	while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
	return ans*f;
}


signed main()
{
	n=read();m=read();r=read();p=read();
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=1;i<n;i++)
	{
		int x,y;
		x=read();y=read();
		add(x,y);add(y,x);
	}
	dfs1(r,0);dfs2(r,r);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int op,x,y,k;
		op=read();
		if(op==1)
		{
			x=read();y=read();k=read();
			update_path(x,y,k);
		}
		else if(op==2)
		{
			x=read();y=read();
			printf("%d\n",query_path(x,y)%p);
		}
		else if(op==3)
		{
			x=read();y=read();
			update_tree(x,y);
		}
		else if(op==4)
		{
			x=read();
			printf("%d\n",query_tree(x)%p);
		}
	}
	return 0;
}

176行的dfs2应该是r,r,我一开始写的r,1,这样一直只过了三个点,其他全T

6.强连通分量Tarjan算法

强连通分量是对于有向图而言,而割点,割边,双连通问题则是对于无向图而言

时间戳dfn[u]:节点u第一次能被访问的顺序

追溯值low[u]:从节点u出发,所能访问到的最早的时间戳

https://www.luogu.com.cn/problem/P2863

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=5e4+10;

int head[maxn],tot;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int dfn[maxn],low[maxn],num;
int stk[maxn],instk[maxn],top;
int scc[maxn],sz[maxn],cnt;

void tarjan(int x)
{
	dfn[x]=low[x]=++num;
	stk[++top]=x,instk[x]=1;
	for(int i=head[x];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(instk[v])
		{
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x])//x是scc的根
	{
		int y;
		++cnt;
		do
		{
			y=stk[top--];
			instk[y]=0;
			scc[y]=cnt;
			++sz[cnt];
		}while(y!=x);
	}
}

int main()
{
	int n,m;
	cin>>n>>m;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=cnt;i++)
	{
		if(sz[i]>1) ans++;
	}
	printf("%d\n",ans);
	return 0;
}

1.校园网络

https://www.luogu.com.cn/problem/P2812

对于带环的算法,我们通常采用Tarjan算法缩点,然后再进行之后的操作

对于第一小问,求至少要多少个网络共享器即为找入度为0的强连通分量的个数

第二小问,求加边的数量,则是对入度为0的个数和出度为0的个数取max即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 5000010
using namespace std;

int head[maxn],tot;
int dfn[maxn],low[maxn];
int stk[maxn],instk[maxn],scc[maxn],cnt,num,top;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

int din[maxn],dout[maxn];

void tarjan(int u)
{
	dfn[u]=low[u]=++num;
	stk[++top]=u,instk[u]=1;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instk[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		int y;
		++cnt;
		do
		{
			y=stk[top--];
			instk[y]=0;
			scc[y]=cnt;
		}while(u!=y);
	}
}

int main()
{
	int n;
	cin>>n;
	memset(head,-1,sizeof(head));
	int x;
	for(int i=1;i<=n;i++)
	{
		while(cin>>x,x) add(i,x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) tarjan(i);
	}
	for(int u=1;u<=n;u++)
	{
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(scc[u]!=scc[v])
			{
				din[scc[v]]++;
				dout[scc[u]]++;
			}
		}
	}
	int a=0,b=0;
	for(int i=1;i<=cnt;i++)
	{
		if(!din[i]) a++;
		if(!dout[i]) b++;
	}
	printf("%d\n",a);
	if(cnt==1) printf("0\n");
	else printf("%d\n",max(a,b));
	return 0;
}

2.割点

对于一个无向图,如果把一个点删除后,连通块的个数增加了

条件是low[y]>=dfn[x]时,x点需要割掉(也就是它的子结点的回溯值不能回到比x还小的地方了,所以删掉这个点,一定会成为一个新的连通块)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=2e5+10;
int dfn[maxn],low[maxn];
int cut[maxn],cnt,rt,ans;

int head[maxn],tot;
struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

void tarjan(int u)
{
	dfn[u]=low[u]=++cnt;
	int child=0;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])
			{
				child++;
				if(u!=rt||child>1) cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);add(y,x);
	}
	for(rt=1;rt<=n;rt++)
	{
		if(!dfn[rt]) tarjan(rt);
	}
	for(int i=1;i<=n;i++)
	{
		if(cut[i]) ans++;
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
	{
		if(cut[i]) printf("%d ",i);
	}
	printf("\n");
	return 0;
}

3.割边

割边:删掉这条边之后图的连通块数量增加了

https://www.luogu.com.cn/problem/P1656

(但其实这道题数据范围很小,用枚举加并查集也可做)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=1e4+10;
int head[maxn],tot=2;
int dfn[maxn],low[maxn],num,cnt;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

struct Bridge
{
	int x,y;
	bool operator < (const Bridge &rhs) const{
		if(x==rhs.x) return y<rhs.y;
		else return x<rhs.x;
	}
}bri[maxn];

void Tarjan(int u,int in_edge)
{
	dfn[u]=low[u]=++num;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			Tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
			{
				bri[++cnt]=Bridge{min(u,v),max(u,v)};
			}		
		}
		else if(i!=(in_edge^1))//不是反边
			low[u]=min(low[u],dfn[v]);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);add(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) Tarjan(i,0);
	}
	sort(bri+1,bri+cnt+1);
	for(int i=1;i<=cnt;i++) printf("%d %d\n",bri[i].x,bri[i].y);
	return 0;
}

4.eDCC缩点

无向图中不包含割边的极大连通块被称为“边双连通分量”

eDCC缩点,将边双连通分量缩为一个点,缩完点后得到的图一定是一颗树(或者森林),树边就是原来的割边,之后就可以观察树,构造答案

https://www.luogu.com.cn/problem/P8436

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=4e6+10;
int head[maxn],tot=2;
int dfn[maxn],low[maxn],num,dcc[maxn],cnt;
int stk[maxn],instk[maxn],top;
int bri[maxn];
vector<int> v[maxn];

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

void Tarjan(int u,int in_edge)
{
	dfn[u]=low[u]=++num;
	stk[++top]=u,instk[u]=1;
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			Tarjan(v,i);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
			{
				bri[i]=bri[i^1]=1;
			}		
		}
		else if(i!=(in_edge^1))//不是反边
			low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++cnt;
		int y;
		do
		{
			y=stk[top--];
			instk[y]=0;
			dcc[y]=cnt;
			v[cnt].push_back(y);
		}while(y!=u);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);add(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) Tarjan(i,0);
	}
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	{
		printf("%d ",v[i].size());
		for(int j=0;j<v[i].size();j++) printf("%d ",v[i][j]);
		printf("\n");
	}
	return 0;
}

https://vjudge.net/problem/POJ-3177

这道题加边的数量就是(sum+1)/2,其中sum是指缩点后叶节点的数量(两两加)

5.vDCC缩点

如果一张图不存在割点,那么称它为“点双连通图”

无向图的极大点双连通子图被称为“点双连通分量”

注意“要对割点进行裂点

也就是说点双连通和边双连通的区别是点双连通是去掉一个点之后连通边双连通是去掉一条边之后连通

vDCC缩点之后,把缩点和对应的割边连边,构成一棵树或者森林,然后观察树,构造答案等

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=4e6+10;
int head[maxn],tot;
int dfn[maxn],low[maxn],num,cnt;
int stk[maxn],instk[maxn],top;
vector<int> dcc[maxn];
int sz[maxn],cut[maxn],rt;
int id[maxn],sum;

struct Edge
{
	int v,nxt;
	Edge(){}
	Edge(int v,int nxt):v(v),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v)
{
	ed[tot]=Edge(v,head[u]);
	head[u]=tot++;
}

void Tarjan(int u)
{
	dfn[u]=low[u]=++num;
	int child=0;
	stk[++top]=u;instk[u]=1;
	if(!sz[u])//u是一个孤立点
	{
		dcc[++cnt].push_back(u);
		return;
	}
	for(int i=head[u];~i;i=ed[i].nxt)
	{
		int v=ed[i].v;
		if(!dfn[v])
		{
			Tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])
			{
				child++;
				if(u!=rt||child>1) cut[u]=1;
				cnt++;
				int y;
				do
				{
					y=stk[top--];
					instk[y]=0;
					dcc[cnt].push_back(y);
				}while(y!=v);
				dcc[cnt].push_back(u);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		if(x==y) continue;
		add(x,y);add(y,x);
		sz[y]++;
		sz[x]++;
	}
	for(rt=1;rt<=n;rt++)
	{
		if(!dfn[rt]) Tarjan(rt);
	}
	sum=cnt;
	/*for(int i=1;i<=n;i++)
	{
		if(cut[i]) id[i]=++cnt;
	}
	//建新图,从每个vDCC相对应割点连边
	for(int i=1;i<=cnt;i++)
	{
		for(int j=0;j<dcc[i].size();j++)
		{
			int u=dcc[i][j];
			if(cut[u])
			{
				Add(i,u);
				Add(u,i);
			}
		}
	}*/
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	{
		printf("%d ",dcc[i].size());
		for(int j=0;j<dcc[i].size();j++) printf("%d ",dcc[i][j]);
		printf("\n");
	}
	return 0;
}

注意:此算法不能处理有自环的情况,所以我们需要判断x==y,此时continue

posted on   dolires  阅读(5)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示