强连通分量&hdu_1269&Codeforce 369D
强连通分量
标签: 图论
算法介绍
还记得割点割边算法吗。回顾一下,tarjan算法,dfs过程中记录当前点的时间戳,并通过它的子节点的low值更新它的low,low值是这个点不通过它的父亲节点最远可以到达的dfn值最小的点,如果当前的节点的low>他父亲节点的dfn说明它不能通过其他的边到达它的父亲,那么它和他父亲的这条边就是割边,在这个算法中有三个标号,VIS数组,标记为0表示没有被访问到,标记为1表示这个点正在搜索它的自孩子,标记为2表示这个点已经处理过了,就是已经找到了它属于哪个联通块,那么它就相当于是剥离出去了,不能再用这个点去跟新它的父亲节点了,下面给一个图,可以自己模拟一下,第一次从1开始进入,第二次从7开始进入。
要注意这个就可以用来缩点了,考虑一下,缩点以后的图是一个拓扑图,就可以进行一系列很好的操作了
通过一个模板题给出模板和讲解
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 10008;
const int M = 100008;
struct Edge{
int to;
int next;
}edge[M];
int head[N];
int Ecnt;
void init()
{
Ecnt = 0;
memset(head,-1,sizeof(head));
}
void addEdge(int x, int y)
{
edge[Ecnt].to = y;
edge[Ecnt].next = head[x];
head[x] = Ecnt++;
}
int top;//指向栈顶的指针
int Stack[N];//维护一个栈
bool instack[N];//instack[i]为真表示i在栈中
int Dfn[N],Low[N];//Dfn[i]表示i在dfs中的时间戳,LOW[i]表示表示dfs可以到达的最小的时间节点,换句话说就是这个节点不通过它的父亲节点最远可以到达的位置,具有相同的low值得点就是在一个联通分量。
int Belong[N];//表示I这个点属于第a个连通分量
int Bcnt,Dindex;//Bcnt用来记录连通分量的个数,Dindex表示到达这个点的时间
void tarjan(int u)
{
int v;
Dfn[u] = Low[u] = ++Dindex;//这里要注意Dindex是初始化为0,不能Dindex++,不然第一个点的DFN和LOW就是0
Stack[++top] = u;//将第一个点压栈
instack[u] = true;//标记在栈里
for( int i = head[u]; i != -1; i = edge[i].next)//dfs的过程
{
int v = edge[i].to;//和这个点相连的一个点
if(!Dfn[v]) //用来更新LOW,相当于割点割边算法中VIS标记为0
{
tarjan(v);//可以获得他的子节点的low值,然后用子节点的low更新他的
if(Low[v] < Low[u])//这个点可以通过它的子节点到达编号更小的位置
Low[u] = Low[v];//更新它
}
else if(instack[v]&&Dfn[v] < Low[u])//如果子节点已经被访问了那么现在这个已经在栈里的元素一定是正在被扫描的,相当于割点割边算法中VIS标记为1,这个时候它的low还没有算出来,但是可以如果dfn如果比当前点dfn小的话,说明这个点还是可以到达比它编号更小的点,更新他
Low[u] = Dfn[v];
}
if(Dfn[u] == Low[u])
{
Bcnt++;//强连通个数加一
do{
v = Stack[top--];//将一个联通块中的点出队
instack[v] = false;//还要标记为未访问的原因是相当于割点割边算法中的VIS标记为2,防止已经被其他强连通分量中找到的处理完的点再次被更新
Belong[v] = Bcnt;
}
while(u!=v);//一直到v = u 都是属于第Bcnt的联通分量
}
}
void solve(int n)
{
int i;
top = Bcnt = Dindex = 0;
memset(Dfn,0,sizeof(Dfn));
for(i = 1; i <= n; i++){
if(!Dfn[i])
tarjan(i);
}
}
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0) return 0;
int x,y;
init();
for(int i = 0; i < m; i++)
{
scanf("%d%d",&x,&y);
addEdge(x,y);
}
solve(n);
if(Bcnt==1) puts("Yes");
else puts("No");
}
return 0;
}
模板
int top;//这个是用作栈顶的指针
int Stack[MAX];//维护的一个栈
bool instack[MAX];//instack[i]为真表示i在栈中
int DFN[MAX],LOW[MAX];
int Belong[MAX];//Belong[i] = a; 表示i这个点属于第a个连通分量
int Bcnt,Dindex;//Bcnt用来记录连通分量的个数,Dindex表示到达某个点的时间
void tarjan(int u)
{
int v;
DFN[u]=LOW[u] = ++ Dindex;//这里要注意 Dindex是初始化为0,这里就不能 Dindex++; 不然第一个点的DFN和LOW就为0
Stack[++ top] = u;
instack[u] = true;
for (edge *e = V[u] ; e ; e = e->next)//对所有可达边的搜索
{
v = e->t;
if (!DFN[v])//这个if 就是用来更新LOW[u]
{
tarjan(v);
if (LOW[v] < LOW[u])
LOW[u] = LOW[v];
}
else if (instack[v] && DFN[v] < LOW[u])
LOW[u] = DFN[v];
}
if (DFN[u] == LOW[u])//这里表示找完一个强连通啦
{
Bcnt ++;//强连通个数加1
do
{
v = Stack[top --];
instack[v] = false;
Belong[v] = Bcnt;
}
while (u != v);//一直到v=u都是属于第Bcnt个强连通分量
}
}
void solve()
{
int i;
top = Bcnt = Dindex = 0;
memset(DFN,0,sizeof(DFN));
for (i = 1; i <= N ; i ++)//这里是一定要对所有点tarjan才能求出所有的点的强连通分量
if (!DFN[i])
tarjan(i);
}
下面也是一个模板题
题意: 给一个数组,a[i]表示从i到a[i]有一条有向边,然后给一个图问你通过反转边的方式来去掉图中的所有环有多少种情况
题解: 我们考虑如果有一个环,那么破坏掉这个环的情况有2^n-2每个边反转或者不反转,最后减去都反转或者都不反转的情况,那么问题来了,会不会反转以后出现产生新的环的情况,答案是否定的,因为原图中每个点都有一个唯一的出度,所以,不可能存在环交的情况,那么如果有一个点孤立的没有环,它可以反转也可以不反转,乘2即可,所以用强连通分量来求出所有的强连通分量,每一个分量都是一个环,最后根据环中的点数来计算总数就可以了
官方题解:
We want to find the number of ways to assign directions to the edges of the graph so that it is acyclic, i.e. contains no cycles. First, we investigate what the graph looks like. It can be easily seen that the graph consists of several connected components, where each component is either a path or a cycle with some paths connected to some of the vertices. We'll solve the problem for each component and multiply the result for each component. If the component is a path, then however we orient the edges we won't form a cycle. So, if there are x edges in the component, there are \(2x\) ways to orient the edges. Next, if the component has a cycle, then we can orient the remaining edges in any way (it will not affect the existence of cycles), then for the \(x\) edges on the cycle, we have \(2^x-2\) ways to orient them. This is because there are a total of \(2x\) ways to orient the edges, but only 2 ways gives a directed cycle. (fix the direction of one of the edges, then the directions of the other edges are uniquely determined to form a directed cycle) Thus, if there are t edges not on the cycle and x edges on the cycle, the total number of ways for this component is \(2^t(2^x - 2)\). Finally, to compute the final answer we multiply the answers for each component. Computing the powers of 2 can be done by a simple precomputation or using binary exponentiation. (the former works because the number of vertices is small) Finding the cycles can be easily done with a dfs.
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 400004;
#define ll long long
const ll Mod = 1000000007;
int Stop, Bcnt, Dindex, Ecnt;
int Low[N], Dfn[N], Step[N], Belong[N];
bool instack[N];
struct Edge{
int t;
int next;
}edge[N];
int head[N];
void addEdge(int s, int t){
edge[Ecnt].t = t;
edge[Ecnt].next = head[s];
head[s] = Ecnt++;
}
void tarjan(int i)
{
int j;
Dfn[i] = Low[i] = ++Dindex;
instack[i] = true;
Step[++Stop] = i;
for( int e = head[i]; e!=-1; e = edge[e].next)
{
j = edge[e].t;
if(!Dfn[j]){
tarjan(j);
if(Low[j]<Low[i])
Low[i] = Low[j];
}
else if(instack[j]&&Dfn[j]<Low[i])
Low[i] = Dfn[j];
}
if(Dfn[i]==Low[i])
{
Bcnt++;
do{
j = Step[Stop--];
instack[j] = false;
Belong[j] = Bcnt;
}
while(j!=i);
}
}
void init()
{
Ecnt = 0;
memset(head,-1,sizeof(head));
}
void solve(int n)
{
int i;
Stop = Bcnt = Dindex = 0;
memset(Dfn,0,sizeof(Dfn));
memset(Low,-1,sizeof(Low));
memset(instack,0,sizeof(instack));
for( i = 1; i <= n; i++)
{
if(!Dfn[i]){
tarjan(i);
}
}
}
ll num[N];
ll Pow(int x, int y)
{
ll a = (ll)x;
ll b = y;
ll ans = 1;
if(b==0) return 1ll;
while(b>0)
{
if(b&1){
ans = (ans*(ll)a)%Mod;
}
a*=a;
a%=Mod;
b>>=1;
}
return ans;
}
int main()
{
int n, x;
while(~scanf("%d",&n))
{
init();
for(int i = 1; i <= n; i++)
{
scanf("%d",&x);
addEdge(i,x);
}
solve(n);
memset(num,0,sizeof(num));
for(int i = 1; i <= n; i++)
{
num[Belong[i]]++;
}
ll ans = 1;
for(int i = 1; i <= n; i++)
{
if(num[i]){
if(num[i] == 1) {
ans = (ans*2)%Mod;
}
else {
ll tm = ((Pow(2,num[i])-2)%Mod+Mod)%Mod;
ans = ans*tm%Mod;
}
}
}
printf("%I64d\n",ans);
}
return 0;
}
强连通分量&hdu_1269&Codeforce 369D的更多相关文章
- codeforce 427 C. Checkposts(tarjan 强连通分量)
题目链接:http://codeforces.com/contest/427/problem/C 题目大意是有n个junctions,这些junctions之间有m条道路,两两相连,现在在juncti ...
- HDU5934 强连通分量
题目:http://acm.hdu.edu.cn/showproblem.php?pid=5934 根据距离关系建边 对于强连通分量来说,只需引爆话费最小的炸弹即可引爆整个强连通分量 将所有的强连通分 ...
- POJ1236Network of Schools[强连通分量|缩点]
Network of Schools Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 16571 Accepted: 65 ...
- 有向图的强连通分量的求解算法Tarjan
Tarjan算法 Tarjan算法是基于dfs算法,每一个强连通分量为搜索树中的一颗子树.搜索时,把当前搜索树中的未处理的结点加入一个栈中,回溯时可以判断栈顶到栈中的结点是不是在同一个强连通分量中.当 ...
- Tarjan算法--强连通分量
tarjan的过程就是dfs过程. 图一般能画成树,树的边有三种类型,树枝边 + 横叉边(两点没有父子关系) + 后向边(两点之间有父子关系): 可以看到只有后向边能构成环,即只有第三张图是强连通分量 ...
- 强连通分量的一二三 | | JZOJ【P1232】 | | 我也不知道我写的什么
贴题: 在幻想乡,上白泽慧音是以知识渊博闻名的老师.春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄.因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点.人间之 ...
- 有向图强连通分量的Tarjan算法
有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...
- poj2186Popular Cows(Kosaraju算法--有向图的强连通分量的分解)
/* 题目大意:有N个cows, M个关系 a->b 表示 a认为b popular:如果还有b->c, 那么就会有a->c 问最终有多少个cows被其他所有cows认为是popul ...
- Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】
一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...
随机推荐
- Python Web框架(URL/VIEWS/ORM)
一.路由系统URL1.普通URL对应 url(r'^login/',views.login) 2.正则匹配 url(r'^index-(\d+).html',views.index) url(r'^i ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- bzoj 2753: [SCOI2012]滑雪与时间胶囊
Description a180285非常喜欢滑雪.他来到一座雪山,这里分布着M条供滑行的轨道和N个轨道之间的交点(同时也是景点),而且每个景点都有一编号i(1<=i<=N)和一高度Hi. ...
- ElasticSearch 学习记录之ES高亮搜索
高亮搜索 ES 通过在查询的时候可以在查询之后的字段数据加上html 标签字段,使文档在在web 界面上显示的时候是由颜色或者字体格式的 GET /product/_search { "si ...
- docker 保存 加载(导入 导出镜像
tensorflow 的docker镜像很大,pull一次由于墙经常失败.其实docker 可以将镜像导出再导入. 保存加载(tensorflow)镜像 1) 查看镜像 docker images 如 ...
- 微信红包店小程序开发过程中遇到的问题 php获取附近周边商家 显示最近商家
最近公司在做一个项目就是微信红包店.仿照的是微信官方在做的那个红包店的模式.客户抢红包,抢到以后到店消费,消费以后就可以拿到商家的红包了. 项目中的两个难点: 1通过小程序来发红包 这个之前在开发语 ...
- sql sever基本查询语句
查询(*可代表全部)(<>代表不等于于)select 列名 from 表名(,隔开)where 查询条件order by 排序的列名+连接的数据类型必须兼容(结果为字符串数据的连接 , 如 ...
- ldap命令的使用
转自:http://blog.chinaunix.net/uid-20690190-id-4085176.html 增:ldapadd 1)选项: -x 进行简单认证 -D 用来绑定服务器的D ...
- open live writer实现多博客同步发送
转载请注明出处 本文出自Hansion的博客 很多人都有多个博客平台同时使用,如CSDN.博客园.开源中国等,但是在其中一个平台上写完博客,想同样发表到其他平台上,这需要我们复制粘贴或者博客搬家,这往 ...
- Flask 框架 简介
一.Flask介绍 Flask是一个基于Werkzeug,Jinja 2 轻量级的web开发框架, 使用Python开发, 上手简单. 二.安装Flask 三.第一个Flask程序 1.编写app.p ...