模板汇总2.4_图论4

1.拓扑排序

①拓扑排序是什么

拓扑排序经常被用来解决有关于“顺序”的图论问题,其本质是对图的一种操作,可以判断一张有向图中是否有环。拓扑排序会将一张图$G$中所有顶点排成一个线性序列,在这个线性序列中,对于图中任意一条边$(u,v)∈E(G)$,使得$u$在这个线性序列中出现在$v$之前,这个线性序列叫做图的拓扑序列。

拓扑排序是针对有向图的算法。

②拓扑排序的原理

拓扑排序有两种实现方式:BFS和DFS,分别这样实现:

BFS:通过队列实现

①将所有入度为零的点入队

②取出队头的点$tn$,弹掉队头

③遍历并删去由$tn$连出的所有边,所谓删去是在度数意义上的,即是将这些边指向的点的入度减去$1$

④检查这些边指向的点,将其中入度为零的点入队

在算法结束后,检查所有的点的入度,如果全部为零说明图中不存在环

输出序列:每有一个点入队就输出这个点

DFS:通过递归实现

①遍历所有的点,如果一个点未被访问过,则从这个点开始搜索

②进入DFS函数:

(1)判断这个点是否被访问过,如果被访问过,回溯,否则将这个点标记为访问过

(2)遍历由这个点连出的所有边,如果发现连出的这些边所指向的点有已经被访问过的,说明有环,否则依次标记并从这些点继续搜索

(3)将这个点压入一个栈中

输出序列:不断输出栈顶元素并弹栈直到栈空

③拓扑排序的时间复杂度

时间复杂度:$O(n+m)$

④拓扑排序的具体实现

 1 //BFS----topsort
 2 #include<queue>
 3 #include<cstdio>
 4 using namespace std;
 5 const int N=100005,M=500005;
 6 int p[N],noww[M],goal[M],deg[N];
 7 int n,m,t1,t2;
 8 bool cir;
 9 queue<int> qs;
10 int main ()
11 {
12     scanf("%d%d",&n,&m);
13     for(int i=1;i<=m;i++)
14     {
15         scanf("%d%d",&t1,&t2);
16         link(t1,t2),deg[t2]++;
17     }
18     for(int i=1;i<=n;i++)
19         if(!deg[i]) qs.push(i);//output the number
20     while(!qs.empty())
21     {
22         int tn=qs.front();qs.pop();
23         for(int i=p[tn];i;i=noww[i])
24             if(!(--deg[goal[i]]))
25                 qs.push(goal[i]);//output the number
26     }
27     for(int i=1;i<=n;i++)
28         if(deg[i]) cir=true;
29     return 0;
30 }
View Code
 1 //DFS----topsort
 2 #include<stack>
 3 #include<cstdio>
 4 using namespace std;
 5 const int N=100005,M=500005;
 6 int p[N],noww[M],goal[M];
 7 int n,m,t1,t2;
 8 bool vis[N];
 9 bool cir;
10 stack<int> st;
11 void DFS(int nde)
12 {
13     if(vis[nde]) return;
14     vis[nde]=true;
15     for(int i=p[nde];i;i=noww[i])
16         if(vis[goal[i]]) {cir=true;return ;}
17         else DFS(goal[i]);
18     st.push(nde);
19 }
20 int main ()
21 {
22     scanf("%d%d",&n,&m);
23     for(int i=1;i<=m;i++)
24         scanf("%d%d",&t1,&t2),link(t1,t2);
25     for(int i=1;i<=n;i++)
26         if(!vis[i]) DFS(i);
27     //while(the stack is not empty) output the top of the stack,pop the stack 
28     return 0;
29 }
View Code

2.2-sat

①什么是2-sat

2-sat,即2-satisfiablity,“双可行性”问题。所谓可行性问题(即satisfiablity)是这样的一类问题:给出若干集合,和一些从集合里取元素的规则,要你判断是否有可行解。而前面的“$2$”则表示一个集合里最多有$2$个元素,由此你就可以明白什么叫3-sat,什么叫4-sat等等。不过多说一句,$3-sat$已经被证明是$NPC$问题了。

②2-sat问题的求解原理

将每个变元拆成是/非两个点,对于每个型如若$a$则$b$的命题,由$a$向$b$连一条有向边,同时要对隐含条件:这个命题的逆否命题进行连边。所谓逆否命题就是字面意思,先逆再否;比如下面这个命题:

若 ydnhaha 模拟赛爆零了,则 i207M 就会 AK 模拟赛

其逆否命题为:

若 i207M 没有AK模拟赛,则 ydnhaha 模拟赛没有爆零 

这些处理结束后,我们可以通过Tarjan尝试求强联通分量。若每个变元所拆出的两点都不在一个SCC里,则有可行解。可行解可以这样构造:将SCC缩点,对新图做反向的拓扑排序,同时黑白染色。但是更简单的方法是直接比较节点所在SCC编号的大小,我们用$i$表示一个变元为真,$i+n$表示一个变元为假,那么当$col[i]<col[i+n]$时变元$i$为真,否则为假,原理是这样也符合拓扑序(我不知道怎么证)

③求解2-sat问题的时间复杂度

时间复杂度:$O(n+m)$(就是Tarjan的复杂度)

④求解2-sat问题的具体方法

洛谷板子

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=2000005;
 6 int dfn[N],low[N],ins[N],stk[N],col[N];
 7 int p[N],noww[2*N],goal[2*N];
 8 int n,m,c,cnt,tot,top,t1,t2,t3,t4;
 9 void link(int f,int t)
10 {
11     noww[++cnt]=p[f];
12     goal[cnt]=t,p[f]=cnt;
13 }
14 void Tarjan_SCC(int nde)
15 {
16     dfn[nde]=low[nde]=++tot;
17     stk[++top]=nde,ins[nde]=true;
18     for(int i=p[nde];i;i=noww[i])
19         if(!dfn[goal[i]])
20             Tarjan_SCC(goal[i]),low[nde]=min(low[nde],low[goal[i]]);
21         else if(ins[goal[i]])
22             low[nde]=min(low[nde],dfn[goal[i]]);
23     if(dfn[nde]==low[nde])
24     {
25         int tmp; c++;
26         do
27         {
28             tmp=stk[top--];
29             ins[tmp]=false;
30             col[tmp]=c;
31         }while(tmp!=nde);
32     }
33 } 
34 int main ()
35 {
36     scanf("%d%d",&n,&m);
37     for(int i=1;i<=m;i++)
38     {
39         scanf("%d%d%d%d",&t1,&t2,&t3,&t4);
40         if(t2&&t4) link(t1+n,t3),link(t3+n,t1);
41         else if(t2) link(t1+n,t3+n),link(t3,t1);
42         else if(t4) link(t1,t3),link(t3+n,t1+n);
43         else link(t1,t3+n),link(t3,t1+n);
44     }
45     for(int i=1;i<=2*n;i++)
46         if(!dfn[i]) Tarjan_SCC(i);
47     for(int i=1;i<=n;i++)
48         if(col[i]==col[i+n])
49             printf("IMPOSSIBLE"),exit(0);
50     printf("POSSIBLE\n");
51     for(int i=1;i<=n;i++)
52         printf("%d ",col[i]<col[i+n]);  
53     return 0;
54 }
View Code

3.LCA

①什么是LCA

LCA即树上两点最近公共祖先

②各种求LCA的方法

倍增求LCA

跳的过程用倍增优化

树剖求LCA

见树链剖分模板

RMQ LCA

搞出来欧拉序之后变成RMQ问题

③时间复杂度

倍增 预处理$O(n\log$ $n)$,单次查询$O(\log n)$

树剖 预处理$O(n)$,单次查询$O(\log n)$

RMQ LCA  预处理$O(n\log n)$,单次查询$O(1)$

④具体实现

倍增

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=500005,K=20;
 6 int p[N],noww[2*N],goal[2*N];
 7 int dep[N],mul[N][K]; 
 8 int n,q,r,t1,t2,cnt;
 9 void link(int f,int t)
10 {
11     noww[++cnt]=p[f];
12     goal[cnt]=t,p[f]=cnt;
13 }
14 void DFS(int nde,int fth,int dth)
15 {
16     dep[nde]=dth,mul[nde][0]=fth;
17     for(int i=1;mul[nde][i-1];i++) 
18         mul[nde][i]=mul[mul[nde][i-1]][i-1];
19     for(int i=p[nde];i;i=noww[i])
20         if(goal[i]!=fth)
21             DFS(goal[i],nde,dth+1);
22 }
23 int LCA(int x,int y)
24 {
25     if(dep[x]<dep[y]) swap(x,y); 
26     for(int i=19;dep[x]!=dep[y];i--)
27         if(dep[mul[x][i]]>=dep[y]) x=mul[x][i];
28     if(x==y) return x;
29     for(int i=19;~i;i--)
30         if(mul[x][i]!=mul[y][i]) 
31             x=mul[x][i],y=mul[y][i];
32     return mul[x][0];
33 }
34 int main ()
35 {
36     scanf("%d%d%d",&n,&q,&r);
37     for(int i=1;i<n;i++)
38     {
39         scanf("%d%d",&t1,&t2);
40         link(t1,t2),link(t2,t1);
41     }
42     DFS(r,0,0),dep[0]=-1;
43     while(q--)
44     {
45         scanf("%d%d",&t1,&t2);
46         printf("%d\n",LCA(t1,t2));
47     }
48     return 0;
49 }
View Code

RMQ

注意欧拉序开两倍点数存

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=5e5+50,M=1e6+60,K=20;
 7 int p[N],noww[M],goal[M];
 8 int dfn[N],idf[N],fir[N],st[M][K];
 9 int n,m,r,t1,t2,cnt,dfo,app;
10 void Link(int f,int t)
11 {
12     noww[++cnt]=p[f];
13     goal[cnt]=t,p[f]=cnt;
14     noww[++cnt]=p[t];
15     goal[cnt]=f,p[t]=cnt;
16 }
17 void DFS(int nde,int fth)
18 {
19     idf[dfn[nde]=++dfo]=nde;
20     st[fir[nde]=++app][0]=dfo;
21     for(int i=p[nde];i;i=noww[i])    
22         if(goal[i]!=fth)
23             DFS(goal[i],nde),st[++app][0]=dfn[nde];
24 }
25 int LCA(int x,int y)
26 {
27     x=fir[x],y=fir[y];
28     if(x>y) swap(x,y);
29     int l2=log2(y-x+1);
30     return idf[min(st[x][l2],st[y-(1<<l2)+1][l2])];
31 }
32 int main ()
33 {
34     scanf("%d%d%d",&n,&m,&r);
35     for(int i=1;i<n;i++)
36         scanf("%d%d",&t1,&t2),Link(t1,t2);
37     DFS(r,0);
38     for(int i=1;i<=19;i++)
39            for(int j=1;j+(1<<i)-1<=app;j++)
40                st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
41     for(int i=1;i<=m;i++)
42     {
43         scanf("%d%d",&t1,&t2);
44         printf("%d\n",LCA(t1,t2));
45     }
46     return 0;
47 }
48     
View Code

树剖的见树剖板子

posted @ 2018-09-18 17:07  Speranza_Leaf  阅读(202)  评论(2)    收藏  举报