联通分量 [割点·割边]

魔板

割点:

void Tarjan(int u,int lst) {
	dfn[u]=low[u]=++Time;
	st[++tp]=u;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
//		int k=i,p=lst^1;
		if(i==(lst^1)) continue;
//		printf("!%d %d\n",u,v);
		if(!dfn[v]) {
			Tarjan(v,i);
			if(!mark[i]&&!mark[i^1])tot++,ans[tot][0]=u,ans[tot][1]=v,mark[i]=1;
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]) flag=1;
		}
		else {
			low[u]=min(low[u],dfn[v]);
			if(!mark[i]&&!mark[i^1])tot++,ans[tot][0]=u,ans[tot][1]=v,mark[i]=1;
		}
	}
}

割边

//此代码没有存fa
void dfs(int u) {
	step++;
	dfn[u]=low[u]=step;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		if(!dfn[v]) {
			fa[v]=u;
			dfs(v);
			if(low[v]>=dfn[u]) {
				if(dfn[u]==1) {son++;}
				else if(!mark[u]) {mark[u]=true;cnt++;}
			}
			low[u]=min(low[u],low[v]);
		}
		else {
			if(fa[u]==v) continue;
			low[u]=min(low[u],dfn[v]);
		}
	}
}

有趣的题目

1.Bertown roads

  • 题意:无向联通图,给边定向,让图成为强连通图
  • 思路:dfs树边+返祖边

2.嗅探器

  • 题意:找到最小编号点,该点为A,B路径的必经点。
  • 思路:脑补求点双联通分量的过程,A,B在dfs树路径的所有割点中最小的,把A当根就可以了。
  • 代码:
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+5;
int cnt,dfn[M],low[M],to[M],son,fa[M],step,nxt[M],head[M],tot;
bool mark[M];
void add_edge(int u,int v) {to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
void dfs(int u) {
	step++;
	dfn[u]=low[u]=step;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		if(!dfn[v]) {
			fa[v]=u;
			dfs(v);
			if(low[v]>=dfn[u]) {
				if(dfn[u]==1) {son++;}
				else if(!mark[u]) {mark[u]=true;cnt++;}
			}
			low[u]=min(low[u],low[v]);
		}
		else {
			if(fa[u]==v) continue;
			low[u]=min(low[u],dfn[v]);
		}
	}
}
int main() {
	int n,a,b,ans=0x3f3f3f3f;
	scanf("%d",&n);
	while(1) {
		int u,v;
		scanf("%d%d",&u,&v);
		if(!u&&!v) break;
		add_edge(u,v); add_edge(v,u);
	}
	scanf("%d%d",&a,&b);
	dfs(a);
	int to=b;
	while(fa[to]!=a) {
		if(dfn[fa[to]]<=low[to]) ans=min(ans,fa[to]);
		to=fa[to];
	}
	if(ans==0x3f3f3f3f) printf("No solution");
	else printf("%d",ans);
	return 0;
}

3.道路*

这道题搞的挺久的~qaq

  • 题目:任何两点间都会进行去来两次外交。问分别删除每个点会使外交减少的次数。
  • 思路:数学柿子:注意要存两个size,size2,因为我的求法中一个点u->v,v的子树不应定受u影响,统计答案的部分式子是要用的这点性质的。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
ll ans[N],size[N],size2[N];
int n,m,dfn[N],low[N],Time,nxt[N],to[N],head[N],cut[N],ecnt=1;
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void Tarjan(int u,int lst) {
	dfn[u]=low[u]=++Time,size[u]=size2[u]=1;
	int son=0;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		if(v==lst)continue;
		if(!dfn[v]) {
			son++;
			Tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]) {
				ans[u]+=1ll*(n-size[v])*size[v];
				cut[u]=1;
				size2[u]+=size[v];
			}
			size[u]+=size[v];
		}
		else {
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(lst==-1&&son==1) cut[u]=0;
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v),add_edge(v,u);
	}
	for(int i=1;i<=n;i++) if(!dfn[i])Tarjan(i,-1);
	for(int i=1;i<=n;i++) {
		if(!cut[i]) printf("%lld\n",1ll*(n-1)*2);
		else printf("%lld\n",1ll*ans[i]+(n-1)+1ll*(n-size2[i])*size2[i]);
	}
	return 0;
}
上一篇:并发编程学习笔记 一 线程中断 两阶段终止模式 线程状态 synchronized原理


下一篇:bootstrap 表格的使用