Tarjan算法
割点(割顶)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define _ 200010
using namespace std;
struct node{int x,y,next;} a[_];
int n,m,len=0,id=0,ans=0;
int last[_],low[_],dfn[_];
/*dfn[i]表示点i被访问的时间戳
low[i]表示点i及i的子树中所有结点能到达的结点中dfn最小的结点的时间戳*/
bool bz[_];//是不是割点
void ins(int x,int y)
{
a[++len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;
}
void dfs(int x,int root)
//x表示当前访问到第x个点,root表示以root为根节点的子树的根
{
int tot=0;//入度(及子树数)
low[x]=dfn[x]=++id;
//记录时间戳
for(int i=last[x];i;i=a[i].next)
{
int y=a[i].y;
if(!dfn[y])
{
dfs(y,root);
low[x]=min(low[x],low[y]);
//更新当前节点的low值
if(low[y]>=dfn[x]&&x!=root) bz[x]=true;
/*非根且子树能达到的dfn最小的结点的时间>=自己的时间时,
说明它的子树中最早能访问到的结点都比它后访问,此时只要不为根就一定是割点*/
if(x==root) tot++;
//更新入度
}
low[x]=min(low[x],dfn[y]);
//把点x及x的子树可以达到的dfn的最小结点更新
}
if(x==root&&tot>=2) bz[root]=true;
/*如果一个点为根且入度>=2(即有两个子树),则一定为割点,
因为一棵树的根一删,那么它的子树一定不连通了*/
}
int main()
{
int x,y;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
ins(x,y);
ins(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i,i);
for(int i=1;i<=n;i++)
if(bz[i]) ans++;
printf("%d\n",ans);//割点的总数
for(int i=1;i<=n;i++)
if(bz[i]) printf("%d ",i);//输出割点
return 0;
}
割边(桥)
#include<bits/stdc++.h>
#define _ 110000
using namespace std;
struct rec
{
int y, next;
}edge[_],cut[_];
int n,m,id=0,len=0;
int dfn[_], low[_];
int head[_], bz[_], link[_];
void Add(rec *edge,int x,int y,int *head)
{
edge[++len].y = y,edge[len].next = head[x],head[x] = len;
}
void tarjan(int x)
{
dfn[x]=low[x]=++id;
for (int i =head[x]; i; i =edge[i].next)
{
int y = edge[i].y;
if (bz[x] == y) continue;
if (!dfn[y])
{
bz[y] = x;//是y的爸爸为x!!
tarjan(y);
low[x] = min(low[x], low[y]);
if (low[y] > dfn[x]) Add(cut, x, y, link);
}//此处无=号!!
low[x] = min(low[x], dfn[y]);
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
int x, y;
cin >> x >> y;
Add(edge,x,y,head);
Add(edge,y,x,head);
}
for (int i = 1; i <= n; ++i)
if (!dfn[i]) tarjan(i);
for (int x = 1; x <= n; ++x)
for (int i = link[x]; i; i = cut[i].next)
printf("%d %d\n",x,cut[i].y);
}
有向图的强连通分量
//强连通分量
#include<bits/stdc++.h>
#define _ 100010
#define $ 1000010
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
int ver[$],Next[$],link[_],len=0;
void add(int x,int y)
{
ver[++len]=y,Next[len]=link[x],link[x]=len;
}
int dfn[_],low[_],id=0,belong[_];
int Stack[_],top=0,tot=0;
int instack[_];
vector<int>scc[_];
void tarjan(int x)
{
dfn[x]=low[x]=++id;
instack[x]=1;
Stack[++top]=x;
for(int i=link[x];i;i=Next[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(instack[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int k;
++tot;
do
{
k=Stack[top--], instack[k]=0;
belong[k]=tot, scc[tot].push_back(k);
}while (k!=x);
}
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
add(x,y);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
for (int i=1;i<=tot;++i)
{
printf("SCC #%d:",i);
for (int j=0;j<scc[i].size();++j)
printf(" %d",scc[i][j]);
puts(" ");
}
return 0;
}
附上一个求最大强连通分量的例题
洛谷 p1726
描述 Description
在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由N个村庄(编号为1…N)和M条道路组成,道路分为两种一种为单向通行的,一种为双向通行的,分别用1和2来标记。如果存在由村庄A到达村庄B的通路,那么我们认为可以从村庄A到达村庄B,记为(A,B)。当(A,B)和(B,A)同时满足时,我们认为A,B是绝对连通的,记为<A,B>。绝对连通区域是指一个村庄的集合,在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。
输入格式 Input Format
第1行:两个正整数N,M
第2…M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。
输出格式 Output Format
第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。
样例输入 Sample Input
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1
样例输出 Sample Output
3
1 3 5
时间限制 Time Limitation
1s
注释 Hint
对于60%的数据:N <= 200且M <= 10,000
对于100%的数据:N <= 5,000且M <= 50,000
#include<bits/stdc++.h>
#define _ 100010
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
int head[_],ver[_],Next[_],len;
void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[_],low[_],id=0;
int belong[_],siz[_];
int Stack[_],top=0,tot=0;
int instack[_];
void tarjan(int x)
{
low[x]=dfn[x]=++id;
instack[x]=1;
Stack[++top]=x;
for(int i=head[x];i;i=Next[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(instack[y])
low[x]=min(low[x],dfn[y]);
}
int k;
if(low[x]==dfn[x])
{
++tot;
do
{
k=Stack[top];
siz[tot]++;
--top;
instack[k]=0;
belong[k]=tot;
} while (k!=x);
}
}
int maxn=-1;
int main()
{
int n=read(),m=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read(),z=read();
add(x,y);
if(z==2) add(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
int dcc;
for(int i=1;i<=n;i++)
if(siz[belong[i]]>maxn)
maxn=siz[belong[i]],dcc=i;
printf("%d\n",maxn);
for(int i=1;i<=n;i++)
if(belong[i]==belong[dcc]) printf("%d ",i);
return 0;
}
无向图的双连通分量
E-DCC (边-双连通分量) 模板
/*
*边双连通分量(e-DCC)的求法
*/
#include<bits/stdc++.h>
#define _ 100010
using namespace std;
int head[_],ver[_*2],Next[_*2];
int dfn[_],low[_],n,m,tot,id;
bool bridge[_*2];
void add(int x,int y)
{
ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
void tarjan(int x,int inEdge)
{
dfn[x]=low[x]=++id;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (!dfn[y])
{
tarjan(y,i);
low[x]=min(low[x],low[y]);
if (low[y]>dfn[x])
bridge[i]=bridge[i^1]=true;
}
else if (i!=(inEdge^1))
low[x]=min(low[x],dfn[y]);
}
}
int c[_],dcc;
void dfs(int x)
{
c[x]=dcc;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (c[y]||bridge[i]) continue;
dfs(y);
}
}
//E-DCC 缩点
int hc[_],vc[_*2],nc[_*2],tc;
void addC(int x,int y)
{
vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc;
}
int main()
{
cin>>n>>m;
tot=1;
for (int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for (int i=1;i<=n;++i)
if (!dfn[i]) tarjan(i,0);
printf("\n");
for (int i=2;i<tot;i+=2)
if (bridge[i])
printf("%d %d\n",ver[i^1],ver[i]);
puts("are cut-edge");
for (int i=1;i<=n;++i)
if (!c[i])
{
++dcc;
dfs(i);
}
printf("There are %d e-DCCs.\n",dcc);
for (int i=1;i<=n;++i)
printf("%d belongs to DCC %d.\n",i,c[i]);
//E-DCC 缩点
tc=1;
for (int i=2;i<=tot;++i)
{
int x=ver[i^1],y=ver[i];
if (c[x]==c[y]) continue;
addC(c[x],c[y]);
}
printf("缩点之后的森林,点数:%d,边数:%d(可能有重边)\n",dcc,tc/2);
for (int i=2;i<=tc;++i)
printf("%d %d\n",vc[i^1],vc[i]);
return 0;
}
V-DCC (点-双连通分量) 模板
/*
*点双连通分量(v-DCC)的求法
*/
#include<bits/stdc++.h>
#define _ 100010
using namespace std;
int head[_],ver[_*2],Next[_*2];
int dfn[_],low[_],Stack[_],top;
int n,m,tot,id,root,cnt;
bool cut[_];
void add(int x,int y)
{
ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
vector<int>dcc[_];
void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
if (x==root&&head[x]==0)//孤立点
{
dcc[++cnt].push_back(x);
return ;
}
int flag=0;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
flag++;
if (x!=root||flag>1) cut[x]=true;
cnt++;
int z;
do
{
z=Stack[top--];
dcc[cnt].push_back(z);
}while (z!=y);
dcc[cnt].push_back(x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int newid[_],hc[_],nc[_*2],vc[_*2],c[_],tc;
void addC(int x,int y)
{
vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc;
}
int main()
{
cin>>n>>m;
tot=1;
for (int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
if (x==y) continue;
add(x,y),add(y,x);
}
for (int i=1;i<=n;++i)
if (!dfn[i]) root=i,tarjan(i);
for (int i=1;i<=n;++i)
if (cut[i]) printf("%d ",i);
puts("are cut-vertexes");
for (int i=1;i<=cnt;++i)
{
printf("e-DCC #%d:",i);
for (int j=0;j<dcc[i].size();++j)
printf(" %d",dcc[i][j]);
puts(" ");
}
//V-DCC 缩点
//给每一个割点一个新的编号(编号从cnt+1开始)
int num=cnt;
for (int i=1;i<=n;++i)
if (cut[i]) newid[i]=++num;
//建新图,从每个V-DCC到它包含的所有割点连边
tc=1;
for (int i=1;i<=cnt;++i)
for (int j=0;j<dcc[i].size();++j)
{
int x=dcc[i][j];
if (cut[x])
addC(i,newid[x]),addC(newid[x],i);
else c[x]=i;//除割点外,其他点仅属1个V-DCC
}
printf("缩点之后的森林,点数:%d,边数:%d\n",num,tc/2);
printf("编号1~%d的为原图的V-DCC,编号>%d的为原图的割点\n",cnt,cnt);
for (int i=2;i<tc;i+=2)
printf("%d %d\n",vc[i^1],vc[i]);
return 0;
}
2-SAT 模板
/*
*luogu.org p4728
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
int n,m;
int ver[maxn<<2],Next[maxn<<2],head[maxn<<1],len;
void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int low[maxn<<1],dfn[maxn<<1],id=0;
int Stack[maxn<<1],belong[maxn<<1],top=0,tot=0;
bool instack[maxn<<1];
void tarjan(int x)
{
low[x]=dfn[x]=++id;
Stack[++top]=x;
instack[x]=1;
for(int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(instack[y])
low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x])
{
int k;
++tot;
do
{
k=Stack[top];
--top;
instack[k]=0;
belong[k]=tot;
}while(k!=x);
}
}
bool two_SAT()
{
for(int i=1; i<=2*n; i++)
if(!dfn[i])
tarjan(i);//tarjan找强连通分量
for(int i=1; i<=n; i++)
if(belong[i]==belong[i+n])//a条件和非a条件在同一个强连通分量,原问题无解
return 0;
return 1;
}
int main()
{
n=read(),m=read();
for(int i=1; i<=m; ++i)
{
int a=read(),aval=read(),b=read(),bval=read();
int nota=aval^1,notb=bval^1;//这里我用a表示条件a选0的情况,a+n表示条件a选1的情况
add(a+nota*n,b+bval*n);//连边(非a,b)
add(b+notb*n,a+aval*n);//连边(非b,a)
}
if(two_SAT())
{
printf("POSSIBLE\n");
for(int i=1; i<=n; ++i)
printf("%d ",belong[i]>belong[i+n]);
}
else printf("IMPOSSIBLE");
return 0;
}
二分图
此类型现在不太会。。。。。。。。。。。。 ,所以先贴模板吧
二分图最大匹配 匈牙利算法 模板
/*洛谷 p3386
题目描述:给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数
输入格式:
第一行,n,m,e,第二至e+1行,每行两个正整数u,v,表示u,v有一条连边(单向),u,v分属两个集合,
1<=u<=n,1<=v<=m,n,m<=1000但是可能有坑
输出格式:共一行,二分图最大匹配
*/
#include<bits/stdc++.h>//注意一点:我们这里把第一个集合的点定为[1,n],第二个集合为[n+1,m]
using namespace std;
const int _=1000010;
int vis[2010],ver[_*2],Next[_*2],head[2010],tot;
int match[2010];
char buf[1<<15],*fs,*ft;
inline char getc(){return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++;}
inline int read()
{
int num=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){num=(num<<3)+(num<<1)+(ch^48);ch=getchar();}
return num*f;
}
inline void add(int x,int y)
{
ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
int dfs(int x)//算法核心
{
for(int i=head[x];i;i=Next[i])
{
int y=ver[i];
if(!vis[y])
{
vis[y]=1;
if((!match[y])||dfs(match[y]))//当前节点的指向节点没有匹配,自然return 1;否则尝试更改指向节点的指向节点的匹配 ,腾出来令当前节点与指向节点匹配
{
match[y]=x;
return 1;
}
}
}
return 0;
}
int main()
{
register int ans=0;
register int n=read(),m=read(),e=read();
for(register int i=1;i<=e;++i)
{
register int u=read(),v=read();
if(u>n||v>m||u<1||v<1) continue;
add(u,v+n);
}
for(register int i=1;i<=n;++i)
{
memset(vis,0,sizeof(vis));//注意清空vis数组
if(dfs(i)) ++ans;
}
/*for(i=n+1;i<=m+n;++i)//输出方案
{
if(match[i]) cout<<match[i]<<" "<<i-n<<endl;
}*/
printf("%d",ans);
return 0;
}
/*补充定义和定理:
*最大匹配数:最大匹配的匹配边的数目
*最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择
*最大独立数:选取最多的点,使任意所选两点均不相连
*最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。
路径长可以为 0(即单个点)。
*最大独立集:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。
找出一个包含顶点数最多的独立集称为最大独立集。
定理1:最大匹配数 = 最小点覆盖数(这是 Konig 定理)
定理2:最大匹配数 = 最大独立数
定理3:最小路径覆盖数 = 顶点数 - 最大匹配数
定理4:最大独立集=所有顶点数-最小顶点覆盖=顶点数-最大匹配数
*/
二分图带权匹配 KM算法 模板
/*
*例题:入门题:HDU2255
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int INF = 0x3f3f3f3f;
int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度
int ex_girl[MAXN]; // 每个妹子的期望值
int ex_boy[MAXN]; // 每个男生的期望值
bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生
bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生
int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1
int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值
int N;
bool dfs(int girl)
{
vis_girl[girl] = true;
for (int boy = 0; boy < N; ++boy)
{
if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
if (gap == 0)
{ // 如果符合要求
vis_boy[boy] = true;
if (match[boy] == -1 || dfs( match[boy] ))
{ // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
match[boy] = girl;
return true;
}
}
else slack[boy] = min(slack[boy], gap);
// slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸】
}
return false;
}
int KM()
{
memset(match, -1, sizeof match); // 初始每个男生都没有匹配的女生
memset(ex_boy, 0, sizeof ex_boy); // 初始每个男生的期望值为0
// 每个女生的初始期望值是与她相连的男生最大的好感度
for (int i = 0; i < N; ++i)
{
ex_girl[i] = love[i][0];
for (int j = 1; j < N; ++j)
ex_girl[i] = max(ex_girl[i], love[i][j]);
}
// 尝试为每一个女生解决归宿问题
for (int i = 0; i < N; ++i)
{
fill(slack, slack + N, INF); // 因为要取最小值 初始化为无穷大
while (1)
{ // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
// 记录每轮匹配中男生女生是否被尝试匹配过
memset(vis_girl, false, sizeof vis_girl);
memset(vis_boy, false, sizeof vis_boy);
if (dfs(i)) break; // 找到归宿 退出
// 如果不能找到 就降低期望值
// 最小可降低的期望值
int d = INF;
for (int j = 0; j < N; ++j)
if (!vis_boy[j]) d = min(d, slack[j]);
for (int j = 0; j < N; ++j)
{ // 所有访问过的女生降低期望值
if (vis_girl[j]) ex_girl[j] -= d;
// 所有访问过的男生增加期望值
if (vis_boy[j]) ex_boy[j] += d;
// 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
else slack[j] -= d;
}
}
}
// 匹配完成 求出所有配对的好感度的和
int res = 0;
for (int i = 0; i < N; ++i)
res += love[ match[i] ][i];
return res;
}
int main()
{
while (~scanf("%d", &N))
{
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
scanf("%d", &love[i][j]);
printf("%d\n", KM());
}
return 0;
}