强连通分量
tarjan
基于深度优先搜索,用于寻找有向图中的连通块。
主要代码如下:
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){//还未访问过这个节点
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){//这个节点在栈中
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){//找到一个强联通分量
++cnt;
while(x!=sta[top]){
vis[sta[top]]=0;
qlt[sta[top]]=cnt;//染色,标记这个节点属于第几号强联通分量
siz[cnt]++;//记录这一个连通块的节点数
top--;
}
vis[x]=0;//还要将这个节点弹出来
qlt[x]=cnt;
siz[cnt]++;
top--;
}
}
如果是要遍历整张图找到所有的连通块,通常还要加这句
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
因为图中不一定全部都是联通的。
每一个点只访问了一次,所以tarjan的时间复杂度是O(n)的。
通常还要用到tarjan缩点。缩点的时候将全部边遍历一次,
如果两个节点不属于同一个连通块,那么就连一条边,为了避免这种情况:
a->c b->c a,b属于同一个连通块。
可以用并查集处理一下,避免重复将两个相同的连通块连边。(重复连边好像也并没有什么影响)
题目
USACO 2003 Fall:
popular cow
这道题首先要知道:对于一个有向无环图,出度为零的点,从其他任意一个点都可以到达这个点。
所以我们只需要将原图用tarjan缩点变成一个有向无环图,找到出度为零的点,输出那个连通块的大小就行了。
#include<bits/stdc++.h>
#define cg c=getchar()
#define N 10020
#define M 50050
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
int n,m,aa,bb;
struct node{
int v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int idx;
int dfn[N];
int low[N];
int sta[N];
int vis[N];
int top;
int cnt;
int qlt[N];
int siz[N];
int cd[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
vis[sta[top]]=0;
qlt[sta[top]]=cnt;
siz[cnt]++;
top--;
}
vis[x]=0;
qlt[x]=cnt;
siz[cnt]++;
top--;
}
}
int main(){
read(n);read(m);
for(int i=1;i<=m;i++){
read(aa);read(bb);
add(aa,bb);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
//进行缩点
for(int i=1;i<=n;i++){
for(int j=head[i];j;j=e[j].nxt){
int v=e[j].v;
if(qlt[i]!=qlt[v]){
cd[qlt[i]]++;//因为不用重建一张图,只需要记录出度就行了
}
}
}
//因为重构的图也有可能存在两个连通块之间没有边连接,它们的出度也为零,所以要考虑这种情况
int ans=0;
int id;
for(int i=1;i<=cnt;i++){
if(cd[i]==0){
ans++;
id=i;
}
}
if(ans==1){//只能有一个出度为零的连通块
print(siz[id]);
}
else print(0);
}
IOI 1996 网络协议
这道题有两个问题,第一个问题还是比较明显,就是缩点后看有几个入度为0的点,每一个入度为零的点都必须放消息。
第二问就相当于将缩点后的图变成一个连通块。每一个入度为零的边都要增加一条入度,每一个出度为零的边都要增加一条出度。最后取较大值。
有个小细节,如果缩点后只有一个点,那么就不用加边了,所以要特判一下。
code:
#include<bits/stdc++.h>
#define N 120
#define M 13000
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
struct node{
int u,v,nxt;
}e[M],e2[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].u=a;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int n;
int aa;
int idx;
int top;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int qlt[N];
int cnt;
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
vis[x]=1;
sta[++top]=x;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
qlt[sta[top]]=cnt;
vis[sta[top]]=0;
top--;
}
qlt[x]=cnt;
vis[x]=0;
top--;
}
}
int rd[N];
int cd[N];
int ans;
int main(){
// freopen("protocols10.in","r",stdin);
read(n);
for(int i=1;i<=n;i++){
while(true){
read(aa);
if(aa==0) break;
add(i,aa);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=tot;i++){
int x=e[i].u;
int y=e[i].v;
if(qlt[x]!=qlt[y]) {
rd[qlt[y]]++;
cd[qlt[x]]++;
}
}
int ans1=0;
int ans2=0;
for(int i=1;i<=cnt;i++){
if(rd[i]==0){
ans1++;
ans++;
}
if(cd[i]==0){
ans2++;
}
}
print(ans);putchar('\n');
if(cnt==1) print(0);//特判 只有一个连通块,不需要加边。
else print(max(ans1,ans2));
}
消息的传递:
跟上一题的第一问一样。。。只不过输入变成了邻接矩阵。
code:
#include<bits/stdc++.h>
#define cg c=getchar()
#define N 1200
#define M N*(N-1)/2
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
struct node{
int u,v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].u=a;
e[tot].v=b;
e[tot].nxt=head[a];
head[a]=tot;
}
int n;
int aa;
/*--------------------------*/
int idx;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int top;
int cnt;
int qlt[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
sta[++top]=x;
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=sta[top]){
qlt[sta[top]]=cnt;
vis[sta[top]]=0;
top--;
}
qlt[x]=cnt;
vis[x]=0;
top--;
}
}
int rd[N];
int main(){
read(n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
read(aa);
if(aa==1){
add(i,j);
}
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=tot;i++){
int x=e[i].u;
int y=e[i].v;
if(qlt[x]!=qlt[y]){
rd[qlt[y]]++;
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
if(rd[i]==0){
ans++;
}
}
print(ans);
}
间谍网络:
这道题跟之前几道差别不大,缩点统计入度就AC了。(我WA了十几次)
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<class T>
inline void Read(T &x)
{
x=0; int s=1; char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-') s=-1; ch=getchar();}
while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();} x*=s;
}
template<class T>
void print(T x)
{
if(x==0) return;
print(x/10);
putchar(x%10+'0');
}
struct node{
int to,next;
};
const int N=3e4+10;
node edge[N<<1];
int len,h[N];
int n,p,val[N],r,in[N],out[N];
int indx,low[N],dfn[N],s[N],top,f[N],cnt,val2[N];
bool visited[N];
void add(int x,int y)
{
edge[++len].to=y; edge[len].next=h[x];
h[x]=len;
}
void tarjan(int u)
{
dfn[u]=low[u]=++indx; s[++top]=u; visited[u]=true;
for(int i=h[u]; i; i=edge[i].next)
{
int to=edge[i].to;
if(!dfn[to])
{
tarjan(to);
low[u]=min(low[to],low[u]);
}
else if(visited[to]) low[u]=min(dfn[to],low[u]);
}
if(dfn[u]==low[u])
{
int v=-1; cnt++;
do{
v=s[top--]; f[v]=cnt; val2[cnt]=min(val2[cnt],val[v]);
visited[v]=false;
}while(u!=v);
}
}
void init()
{
memset(val,0x3f,sizeof(val));
memset(val2,0x3f,sizeof(val2));
scanf("%d%d",&n,&p);
for(int i=1; i<=p; i++)
{
int id,money; Read(id); Read(money);
val[id]=money;
}
Read(r);
for(int i=1; i<=r; i++){
int x,y; Read(x); Read(y);
add(x,y); in[y]++; out[x]++;
}
bool flag=false;
for(int i=1; i<=n; i++)
if(val[i]!=0x3f3f3f3f && out[i]!=0) flag=true;
if(!flag)
{
printf("NO\n%d\n",1); return;
}
for(int i=1; i<=n; i++){
if(!in[i] && val[i]==0x3f3f3f3f){
puts("NO");
printf("%d\n",i);
exit(0);
}
}
memset(in,0,sizeof(in));
for(int i=1; i<=n; i++)
if(!dfn[i]) tarjan(i);
for(int u=1; u<=n; u++)
for(int i=h[u]; i; i=edge[i].next){
int to=edge[i].to;
if(f[u]!=f[to]) in[f[to]]++;
}
int ans=0;
for(int i=1; i<=cnt; i++)
if(!in[i]) ans+=val2[i];
printf("YES\n%d",ans);
}
int main(){
init();
return 0;
}
APIO 2009 抢掠计划
这道题还是比较好想,先tarjan缩点后新建一张图跑最长路就行了。(不知道大佬有没有其他更简单的做法)
code:
#include<bits/stdc++.h>
#define N 500004
#define inf 0x3f3f3f3f
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
struct node{
int u,v,nxt;
}e[N],e2[N];
int head[N];
int tot;
inline void add(int a,int b){
++tot;
e[tot].v=b;
e[tot].u=a;
e[tot].nxt=head[a];
head[a]=tot;
}
int n;
int m;
int aa,bb;
int s,p;
int val[N];
int w[N];
int pub[N];
int jg[N];
/*-------------------------------*/
int idx;
int dfn[N];
int low[N];
int st[N];
int top;
int vis[N];
int cnt;
int qlt[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
st[++top]=x;
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=st[top]){
qlt[st[top]]=cnt;
vis[st[top]]=0;
w[cnt]+=val[st[top]];
if(pub[st[top]]==1) jg[cnt]=1;
top--;
}
qlt[x]=cnt;
vis[x]=0;
w[cnt]+=val[x];
if(pub[x]==1) jg[cnt]=1;
top--;
}
}
/*---------------------------*/
int head2[N];
int tot2;
inline void add2(int a,int b){
++tot2;
e2[tot2].u=a;
e2[tot2].v=b;
e2[tot2].nxt=head2[a];
head2[a]=tot2;
}
/*-0------------------------------*/
int dis[N];
queue<int>q;
inline void spfa(){
for(int i=1;i<=cnt;i++){
dis[i]=0;vis[i]=0;
}
s=qlt[s];
q.push(s);vis[s]=1;dis[s]=w[s];
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head2[x];i;i=e2[i].nxt){
int v=e2[i].v;
if(dis[v]<dis[x]+w[v]){
dis[v]=dis[x]+w[v];
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
int main(){
read(n);read(m);
for(int i=1;i<=m;i++){
read(aa);read(bb);
add(aa,bb);
}
for(int i=1;i<=n;i++) read(val[i]);
read(s);read(p);
for(int i=1;i<=p;i++){
read(aa);
pub[aa]=1;
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=m;i++){
int x=e[i].u;
int y=e[i].v;
if(qlt[x]!=qlt[y]){
add2(qlt[x],qlt[y]);
}
}
spfa();
int ans=-inf;
for(int i=1;i<=cnt;i++){
if(jg[i]==1){
ans=max(ans,dis[i]);
}
}
print(ans);
}
POI 2001 和平委员会
2-sat问题,推荐博客:
https://blog.csdn.net/JarjingX/article/details/8521690
https://blog.sengxian.com/algorithms/2-sat
code:
#include<bits/stdc++.h>
#define cg c=getchar()
#define N 30000
#define M 30000
using namespace std;
template<typename xxx>inline void read(xxx &x){
x=0;int f=1;char cg;
while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
x*=f;
}
template<typename xxx>inline void print(xxx x){
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
vector<int>e[N];
int n,m;
int aa,bb;
inline int other(int x){
if(x%2==0) return x-1;
return x+1;
}
int idx;
int dfn[N];
int low[N];
int st[N];
int top;
int cnt;
int qlt[N];
int vis[N];
inline void tarjan(int x){
dfn[x]=low[x]=++idx;
st[++top]=x;
vis[x]=1;
for(int i=0;i<e[x].size();i++){
int v=e[x][i];
if(!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]==1){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
++cnt;
while(x!=st[top]){
qlt[st[top]]=cnt;
vis[st[top]]=0;
top--;
}
qlt[x]=cnt;
vis[x]=0;
top--;
}
}
int main(){
read(n);read(m);
for(int i=1;i<=m;i++){
read(aa);read(bb);
e[aa].push_back(other(bb));
e[bb].push_back(other(aa));
}
for(int i=1;i<=(n<<1);i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++){
int x=2*i-1;
int y=2*i;
if(qlt[x]==qlt[y]){
printf("NIE");
return 0;
}
}
for(int i=1;i<=n;i++){
int x=2*i-1;
int y=2*i;
if(qlt[x]<qlt[y]){
print(x);putchar(' ');
}
else{
print(y);putchar(' ');
}
}
}
心得
- 有向无环图中一个出度为零的点,从任意一个点出发都可到达它。
- 有向无环图至少有一个入度为零的点。
- 有向无环图的生成树个数等于入度非零的节点的入度积。
- 强联通分量的题好像多数与入度出度有关,想题的时候可以往这方面考虑。