本篇并不适合初学者阅读。
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连边。
连出一个黑点白点相间的树(黑点可以认为是割点)
例题:
分类讨论即可
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 */
附赠福利:
欧拉回路:
void dfs(int x){
for(each son)
if(!vis[i]){
vis[i]=vis[i^1]=1;
dfs(e[i].to)
}
sta[++top]=x;
}
然后sta倒着输出即可。
至于每个点访问多次,可以弧优化减少枚举。