Tarjan

强连通分量

定义:一个有向图的最大强连通子图(子图中任意两点均可互相到达)

dfn(时间戳):节点dfs遍历顺序

low:节点通过非返祖边能到达的最小dfn

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n,m,x,y,top,cnt,t,ans,aass;
//ans为强连通分量包含节点的最大数量,aass为强连通分量数量,n个节点,m条边
int head[N],dfn[N],stac[N],low[N];
bool vis[N];
struct edge{
	int v,next;
}e[1000020];
inline void add(int u,int v){ 
	e[++top] = (edge){v,head[u]};
	head[u] = top;
}
int read(){
	int x = 0,k = 1;
	char c = getchar();
	for(;!isdigit(c);c = getchar())if(c == '-')k = -1;
	for(;isdigit(c);c = getchar())x = (x<<1) + (x<<3) + c - '0';
	return x * k;
}
void tarjan(int now){
	dfn[now] = low[now] = ++cnt;
	stac[++t] = now;
	vis[now] = true;
	for(int i = head[now];~i;i = e[i].next)
		if(!dfn[e[i].v]){
			tarjan(e[i].v);
			low[now] = min(low[now],low[e[i].v]);
		}
		else if(vis[e[i].v])
			low[now] = min(low[now],dfn[e[i].v]);
	if(dfn[now] == low[now]){
		int cur,sum = 0;
		aass++;
		do{
			cur = stac[t--];
			vis[cur] = false;
			printf("%d ",cur);
			sum++;
		}while(now != cur);
		printf("\n");
		ans = max(ans,sum);
	}
}
int main(){
	n = read(),m = read();
	memset(head,-1,sizeof(head));
	for(int i = 1;i <= m;i++){
		x = read(),y = read();
		add(x,y);
	}
	for(int i = 1;i <= n;i++)
		if(!dfn[i])tarjan(i);
	printf("The max point(s) of Strongly Connected Component is : %d\n",ans);
	printf("The number of Strongly Connected Component(s) is(are) : %d",aass);
	return 0;
}

缩点

缩点就是把同一个强连通分量中的点染成同一种颜色,把图变成DAG

核心:

if(low[now] == dfn[now]){
		int cur,sum = 0;
		idx++;
		do {
			cur = stac[top--];
			vis[cur] = false;
			bel[cur] = idx;
			sum += point[cur];
//			for(int i = head[cur];~i;i = e[i].next)
//				dp[idx] = max(dp[idx],dp[bel[e[i].v]]);这两个语句是P3387的dp语句~
		} while(cur != now);
//		dp[idx] += sum;
//		ans = max(ans,dp[idx]);这两句也是
	}

其实再建一张新图也可以

割边(桥)

定义:删去这条边后无向图将不再连通

如果这条边的子节点无法通过非返祖边回到父节点,那么这条边就是割边

if(i == (lastedge ^ 1)) continue;
		if(!dfn[e[i].v]){
			tarjan(e[i].v,i);
			low[now] = min(low[now],low[e[i].v]);
			if(dfn[now] < low[e[i].v])
				p[++ans] = make_pair(min(now,e[i].v),max(now,e[i].v));
		}
		else low[now] = min(low[now],dfn[e[i].v]);

lastedge是返祖边,注意num从2开始

边双连通分量(边双)

就是一个两个点可以从多条不相交的路径互相到达的极大子图

和缩点一样,把同一个边双连通分量中的点染成同一种颜色就可以啦!

if(low[now] == dfn[now]){
		int cur;
		idx++;
		do {
			cur = stac[top--];
			bel[cur] = idx;
		} while(cur != now);
	}

洛谷P8436

void tarjan(int u,int last){
	dfn[u] = low[u] = ++cnt;
	stac[++top] = u;
	for(int i = head[u];i;i = e[i].next){
		if(i == (last ^ 1)) continue;
		if(!dfn[e[i].v]){
			tarjan(e[i].v,i);
			low[u] = min(low[u],low[e[i].v]);
		}
		else low[u] = min(low[u],dfn[e[i].v]);
	}
	if(low[u] == dfn[u]){
		int cur;
		idx++;
		do {
			cur = stac[top--];
			vec[idx].push_back(cur);
		} while(cur != u);
	}
}

割点、点双

割点定义:删去一个点以及其相邻的边后使得图不连通的点

一张没有割点的图为点双连通图,一张图中极大点双连通子图被称为点双连通分量,简称点双

一个 点双连通图 的一个定义是:图中任意两不同点之间都有至少两条点不重复的路径。

一个近乎等价的定义是:不存在割点的图。
这个定义只在图中只有两个点,一条连接它们的边时失效。它没有割点,但是并不能找到两条不相交的路径,因为只有一条路径。

性质:两个点双的交小于等于一。同时属于多个点双的点是割点

void tarjan(int now,int lastedge){
	dfn[now] = low[now] = ++cnt;
	stac[++top] = now;
	for(int i = head[now];~i;i = e[i].next){
		if(i == (lastedge ^ 1)) continue;
		if(!dfn[e[i].v]){
			tarjan(e[i].v,i);
			low[now] = min(low[now],low[e[i].v]);
			if(low[e[i].v] >= dfn[now]){
				idx++;
				deg[now]++;
				int cur;
				do{
					cur = stac[top--];
					deg[cur]++;
				}while(cur != e[i].v);
			}
		}
		else
			low[now] = min(low[now],dfn[e[i].v]);
	}
}//deg记录点的度数

带自环和孤立点的点双(洛谷P8435)

void tarjan(int u,int last){
	dfn[u] = low[u] = ++cnt;
	stac[++top] = u;
	bool flag = true,f2 = true;
	for(int i = head[u];i;i = e[i].next){
		flag = false;
		if(e[i].v != u) f2 = false;
		if(i == (last ^ 1)) continue;
		if(!dfn[e[i].v]){
			tarjan(e[i].v,i);
			low[u] = min(low[u],low[e[i].v]);
			if(low[e[i].v] >= dfn[u]){
				vec[++idx].push_back(u);
				int cur;
				do{
					cur = stac[top--];
					vec[idx].push_back(cur);
				}while(cur != e[i].v);
			}
		}
		else low[u] = min(low[u],dfn[e[i].v]);
	}
	if(flag || f2) vec[++idx].push_back(u);
}

2-SAT

SAT是Satisfiability(可满足性)问题的简写,k-SAT为m组要求,每组要求至少有一个满足即可(或运算),求合法方式。当k > 2时此问题是NP-完全的。

2-SAT为k = 2时的情况。

判断是否有合法方式

设某一组要求为x1 == true || x2 == false;

如果x1 == false可以推出x2 == false;同理,x2 == true可以推出x1 == true。(否则条件不成立)

建图,此时一个强连通分量内部的条件一定同时成立/不成立

如果一个变量的true和flase在同一个强连通分量里,那么无解。

重点:构造解

先传一张图:

x1 == false || x2 == true;

x1 == false || x2 == false;

因为由x1 = true 可以推出 x1 = false,所以x1只能为false(后者不会推出前者)

所以给变量赋值优先用后遍历的值,由于tarjan是弹栈操作,后遍历的点被先弹出来作为一个强连通分量,故优先选bel(强连通分量编号)小的值赋值。

洛谷P4782代码(用i+n表示xi = false)

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define repe(i,u) for(int i = head[u];i;i = e[i].next)
const int N = 2e6 + 5;
struct edge{
	int v,next;
}e[N];
int head[N],dfn[N],low[N],stac[N],bel[N],top,tot,idx,cnt,n,m,x1,x2,a,b;
bool vis[N];
inline void add(int u,int v){
	e[++tot] = (edge){v,head[u]};
	head[u] = tot;
}
void tarjan(int u){
	dfn[u] = low[u] = ++cnt;
	stac[++top] = u;
	vis[u] = true;
	repe(i,u)
		if(!dfn[e[i].v]){
			tarjan(e[i].v);
			low[u] = min(low[u],low[e[i].v]);
		}
		else if(vis[e[i].v]) low[u] = min(low[u],dfn[e[i].v]);
	if(low[u] == dfn[u]){
		idx++;
		int cur;
		do {
			cur = stac[top--];
			bel[cur] = idx;
			vis[cur] = false;
		} while(cur != u);
	}
}
int main(){
	scanf("%d %d",&n,&m);
	rep(i,m){
		scanf("%d %d %d %d",&x1,&a,&x2,&b);
		add(x1 + a * n,x2 + (!b) * n),add(x2 + b * n,x1 + (!a) * n);
	}
	rep(i,n * 2) if(!dfn[i]) tarjan(i);
	rep(i,n) if(bel[i] == bel[i+n]){
		printf("IMPOSSIBLE");
		return 0;
	}
	printf("POSSIBLE\n");
	rep(i,n) printf("%d ",bel[i] < bel[i+n] ? 1 : 0);
	return 0;
}
posted @ 2025-02-10 14:14  离_2012  阅读(19)  评论(0)    收藏  举报