[学习笔记]Tarjan&&欧拉回路

本篇并不适合初学者阅读。

SCC:

1.Tarjan缩点:x回溯前,dfn[x]==low[x]则缩点。

注意:

①sta,in[]标记。

②缩点之后连边可能有重边。

2.应用:

SCC应用范围还是很广的。

基本思路是:Tarjan+topo

DAG是个好东西。

各种判断连通性(传递关系),计数,以及dp转移

感觉90%以上的tarjan都是考SCC

例题:最大半连通子图(这个题注意缩点后的重边)

衍生应用算法:

①2-SAT

②完备匹配的二分图必须边和可行边。

E-DCC

1.Tarjan找桥:x的一个子节点y,dfn[x]<low[y],边就是桥

注意:

①in_edge,对称建边号。不能从in_edge^1出发。但是可以走重边。

然后不经过桥dfs,找E-DCC

2.应用:

①最短路的必经边:建出最短路图,找桥即可。

②例题:poj3694

找出桥边,E-DCC缩点。

对于询问(x,y)x,y不在一个DCC,

找LCA,之后可以往上暴力找路径,干掉桥。

然鹅使用并查集更快。

O(M+N+Qlogn)

V-DCC

1.找割点:dfn[x]<=low[y]不用管重边什么的。

注意:

①搜索树根节点要有两次符合,才是割点。

其实对于割点x,dfn[x]<=low[y]的出点y,这些y一定在不同的DCC中。

缩点:

除了孤立点之外,每个DCC大小至少为2


每个割点属于多个DCC

用栈维护。

如果某个点满足dfn[x]<=low[y]

那么,不断弹出栈顶,直到y弹出。

把这些点和x都放进一个DCC中。(vector存)

可以发现一个x属于多个DCC

缩点的时候,

先把割点设置编号(cnt+1~cnt+tot)cnt是DCC个数

tot是割点个数

循环所有的DCC,循环所有的成员

如果找到割点,就让这个割点和这个DCC连边。

连出一个黑点白点相间的树(黑点可以认为是割点)

例题:

[HNOI2012]矿场搭建

分类讨论即可

KNIGHTS - Knights of the Round Table

性质题,

建立反图。没有仇恨的人连边。

没有出现在任何一个简单奇环中的骑士滚蛋

V-DCC缩点

如果两个骑士DCC不同,一定不能同时出席。(否则在一个奇环中的话,那么两个DCC可以合并。矛盾)

如果一个DCC中有奇环,那么DCC中的所有骑士都可以被一个奇环包含。(可以构造)

综上,一个骑士被包含,因为不能和其他DCC合作,所以必须当且仅当自己的DCC中有奇环

Tarjan+二分图染色判断奇环

代码:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
namespace Miracle{
const int N=1005;
const int M=1e6+5;
bool hate[N][N];
int n,m;
void rd(int &x){
    char ch;x=0;
    while(!isdigit(ch=getchar()));
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
}
struct node{
    int nxt,to;
}e[2*M];
int cnt,hd[N];
void add(int x,int y){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    hd[x]=cnt;
}
bool cut[N];
bool has[N];//for cut
vector<int>mem[N];
int rt;
int be[N];
int sta[N],top;
int dfn[N],low[N];
int df;
int dcc;
void tarjan(int x){
    sta[++top]=x;
    low[x]=dfn[x]=++df;
    bool fl=false;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(dfn[x]<=low[y]){
                ++dcc;
                mem[dcc].clear();
                if(fl||x!=rt) cut[x]=1;
                fl=true;
                int z;
                do{
                    z=sta[top--];
                    be[z]=dcc;
                    mem[dcc].push_back(z);
                }while(z!=y);
                be[x]=dcc;
                mem[dcc].push_back(x);
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int c[N];
bool bla;
bool ok[N];
void che(int x,int las,int id){
//    cout<<" che "<<x<<endl;
    c[x]=las;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        
        if(be[y]!=id) continue;
    //    cout<<" goto "<<y<<endl;
        if(!c[y]){
            che(y,3-las,id);
        }
        else if(c[y]==c[x]) bla=false;
        //if(!fl) return false;
    }
    //return fl;
}
void clear(){
    memset(hate,0,sizeof hate);
    memset(cut,0,sizeof cut);
    memset(hd,0,sizeof hd);cnt=0;
    memset(dfn,0,sizeof dfn);df=0;
    memset(ok,0,sizeof ok);
    dcc=0;
    memset(c,0,sizeof c);
    memset(has,0,sizeof has);
    memset(be,0,sizeof be);
}
int main(){
    while(1)
    {
        scanf("%d%d",&n,&m);int x,y;
        if(n==0&&m==0) break;
        clear();
        for(reg i=1;i<=m;++i){
            rd(x);rd(y);
            hate[x][y]=1;
        }
        for(reg i=1;i<=n;++i){
            for(reg j=i+1;j<=n;++j){
                if(!hate[i][j]){
                    add(i,j);add(j,i);
                }
            }
        }
        for(reg i=1;i<=n;++i){
            if(!dfn[i]) rt=i,top=0,tarjan(i);
        }
//        cout<<" dcc "<<dcc<<endl;
//        for(int i=1;i<=n;++i){
//            cout<<i<<" : "<<cut[i]<<" "<<be[i]<<endl;
//        }cout<<endl;
        for(reg i=1;i<=dcc;++i){
        //    cout<<" i---------- "<<i<<endl;
            memset(c,0,sizeof c);
            for(reg j=0;j<mem[i].size();++j){
        //        cout<<" mem "<<mem[i][j]<<endl;
                    be[mem[i][j]]=i;
            }
            bla=true;
            che(mem[i][0],1,i);
        //    cout<<" bla "<<bla<<endl;
            if(!bla){
                for(reg j=0;j<mem[i].size();++j){
                    ok[mem[i][j]]=1;
                }
            }
        }
        
        int ans=n;
        for(reg i=1;i<=n;++i){
            ans-=ok[i];
        }
        printf("%d
",ans);
    }
    return 0;
}

}
int main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/11/6 7:40:18
*/
View Code

附赠福利:

欧拉回路:

void dfs(int x){

for(each son)
if(!vis[i]){

vis[i]=vis[i^1]=1;

dfs(e[i].to)
}

sta[++top]=x;

}

然后sta倒着输出即可。

至于每个点访问多次,可以弧优化减少枚举。

原文地址:https://www.cnblogs.com/Miracevin/p/9911695.html