精华内容
下载资源
问答
  • 题意:给出有向边,并且...强联通转化:强联通缩点后,若出度为零的点不唯一,则无解,若唯一,联通块的节点数,,, #include #include #include #include #include #include #include #include //#include #

    题意:给出有向边,并且有向边可以传递,即A可以到B,B可以到C,则A可以到C

    问有多少节点可以由其它除其本身的所有节点到达;

    强联通转化:强联通缩点后,若出度为零的点不唯一,则无解,若唯一,求联通块的节点数,,,


    #include<cstdio>
    #include<stdlib.h>
    #include<string.h>
    #include<string>
    #include<map>
    #include<cmath>
    #include<iostream>
    #include <queue>
    //#include <stack>
    #include<algorithm>
    #include<set>
    using namespace std;
    #define INF 1e8
    #define eps 1e-8
    #define LL __int64
    #define maxn 26
    #define mol 1000000007
    #define N 10010
    #define M 50010
    struct Edge  
    {  
    	int v;  
    	int next;  
    };  
    Edge edge[M];//边的集合  
    
    int node[N];//顶点集合  
    int instack[N];//标记是否在stack中  
    int stack[N];  
    int Belong[N];//各顶点属于哪个强连通分量  
    int DFN[N];//节点u搜索的序号(时间戳)  
    int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)  
    int n, m;//n:点的个数;m:边的条数  
    int cnt_edge;//边的计数器  
    int Index;//序号(时间戳)  
    int top;  
    int Bcnt;//有多少个强连通分量  
    int in[N],out[N],numd[N];
    void add_edge(int u, int v)//邻接表存储  
    {  
    	edge[cnt_edge].next = node[u];  
    	edge[cnt_edge].v = v;  
    	node[u] = cnt_edge++;  
    }  
    void tarjan(int u)  
    {  
    	int i, j;  
    	int v;  
    	DFN[u] = LOW[u] = ++Index;  
    	instack[u] = true;  
    	stack[++top] = u;  
    	for (i = node[u]; i != -1; i = edge[i].next)  
    	{  
    		v = edge[i].v;  
    		if (!DFN[v])//如果点v没被访问//树枝边  
    		{  
    			tarjan(v);  
    			if (LOW[v]<LOW[u])  
    				LOW[u] = LOW[v];  
    		}  
    		else//如果点v已经被访问过//后向边  
    			if (instack[v] && DFN[v]<LOW[u])  
    				LOW[u] = DFN[v];  
    	}  
    	if (DFN[u] == LOW[u])//缩点  
    	{  
    		Bcnt++;  
    		do  
    		{  
    			j = stack[top--];  
    			instack[j] = false;  
    			Belong[j] = Bcnt;  
    		} while (j != u);  
    	}  
    }  
    void solve()  
    {  
    	int i;  
    	top = Bcnt = Index = 0;  
    	memset(DFN, 0, sizeof(DFN));  
    	memset(LOW, 0, sizeof(LOW));  
    	memset(Belong, 0, sizeof(Belong)); 
    	for (i = 1; i <= n; i++)  
    		if (!DFN[i])  
    			tarjan(i);  
    }  
    int main()  
    {  
    	//freopen("in.txt", "r", stdin);  
    	int i, j, k, t;  
    	while(~scanf("%d %d", &n,&m))
    	{
    		cnt_edge = 0;  
    		memset(node, -1, sizeof(node));  
    
    		for (i = 1; i <= m; i++)  
    		{  
    			scanf("%d%d", &j,&k);
    				add_edge(j,k);  
    		}  
    		solve();  
    		memset(in,0,sizeof(in));
    		memset(out,0,sizeof(out));
    		memset(numd,0,sizeof(numd));
    		for(i=1;i<=n;i++)
    		{
    			for(j=node[i];j!=-1;j=edge[j].next )
    			{
    				if(Belong[i]!=Belong[edge[j].v])
    				{
    					in[Belong[edge[j].v]]++;
    					out[Belong[i]]++;
    				}
    			}
    		}
    		int a,b=0;
    		for(i=1;i<=Bcnt;i++)
    		{
    			if(!out[i])
    			{
    				b++;
    				a=i;
    			}
    		}
    		if(b!=1) printf("0\n");
    		else
    		{
    			int ans=0;
    			for(i=1;i<=n;i++)
    			{
    				if(Belong[i]==a)
    					ans++;
    			}
    			printf("%d\n",ans);
    		}
    	}
    	return 0;  
    }  
    /*
    3 3
    1 2
    2 1
    2 3
    */
    


    展开全文
  • //在网上看了看其他人的题解,有拿并查集判断去除某个点后两个是否属于同一棵树的,有用DFS判断从某点到某点的方法有几个,顺便记录路上经过点,到达则每个点经过次数...然后 在网上找了个关于强联通的模板(POJ12...

    //在网上看了看其他人的题解,有拿并查集判断去除某个点后两个是否属于同一棵树的,有用DFS判断从某点到某点的方法有几个,顺便记录路上经过点,到达则每个点经过次数加一,最后判断方法和次数是否一致,一致则说明为必经dian ,....也可以用深搜和广搜判断去除某一点后,是否还能抵达.....

    我看了看   好像属于割点,可以用Tarjan做......然后  在网上找了个关于强联通的模板(POJ1236).....求得是所有点组成的图的割点,,,,所以,解法还是和dfs或者bfs差不多,,都是去除某个点,然后判断是否属于同一个数组里......

    但是  应该还是有问题,,只有40分....待解决.............

     

    https://blog.csdn.net/qq_35078631/article/details/54705968的博文也用了Tarjan.....模板都是一样的,不过他新建了一个数组endishere,用来标记起点到终点的割点...标记方法在我看来是十分巧妙的...即不断往下搜索...直到终点,然后标记终点的的上个点,回溯即可将改路径上走过的点标记..之后判断1-n这n个点,被标记并且是割点即可....

    模板应该如下..(能不能过..我没试过...等想写了改下提交试试)

     

    //判断是否为割点
    void FindCutNode(int dep, int u)
    {
    	dfn[u] = low[u] = dep;
    	for(int i=head[u]; ~i; i=edge[i].next){
    		int v = edge[i].v;
    		//if(!dfn[v])//不可以用这个条件....会出错,还是再用个数组标记吧
                if(!vis[v])  {
    		 vis[v] = true;
            	parent[v] = u; 
    
    			FindCutNode(dep+1, v);
    			if(u == rt){//此时访问的节点和 
    				rt_num++;
    if(v==end || endishere[v]==1){    //记录start和end中经过的点,如果子树是end或者子树已经为1则标1 
                    endishere[u]=1;              //即搜索到终点end位置,会标记,然后回溯便可将从起点到终点所有的点标记,,后续做出来里即可,即判断cut[]和endishere[]
                }
    			}
    			     else if(parent[u] != v && dfn[v] < low[u])//v的父节点不能用来跟新他的low值 
                             {
    				low[u] = min(low[u], low[v]);
    				
    				if(low[v] > dfn[u]){
    					cut[u] = true;
    				}
    			} 
    			
    		}
    		else low[u] = min(low[u], dfn[v]);
    	}
    }
    

     

    
    //强联通求割边和割点的问题 
    #include <string.h>
    #include<iostream>
    #include <stdio.h>
    #define V    105
    #define E    100500
    const int inf = 0x3f3f3f;
    using namespace std;
    
    struct edge
    {
        int to, next;
    }Edge[E];
    
    int head[V], e, n, m;
    
    int indeg[V], outdeg[V]; //点的入度和出度数
    int belong[V], low[V], dfn[V], scc, cnt;//dfn[]:遍历到u点的时间; low[]:u点可到达的各点中最小的dfn[v]
    int S[V], top;
    bool vis[V];//v是否在栈中
    bool cut[V];
    int addedge(int u, int v)
    {
        Edge[e].to = v;
        Edge[e].next = head[u];
        head[u] = e++;
        return 0;
    }
    
    //int cont = 0; 
     
    void tarjan(int u)
    {
        int v;
        dfn[u] = low[u] = ++cnt;
        S[top++] = u;
        vis[u] = true;
        for (int i=head[u]; i!=-1; i=Edge[i].next)
        {
            v = Edge[i].to;
            if (dfn[v] == 0)//v点未遍历
            {
                tarjan(v);
             /*   
    			if (low[v] > dfn[u])
                {
                	cont++;
    				cut[u] = true; 
    				cout << u << endl; //如果满足这个条件则u 为此时该图的割点
    			}
                */
    			low[u] = low[u] < low[v] ? low[u] : low[v];//回溯保证low为所联系的最小值
            }
            
            else if (vis[v] && low[u] > dfn[v])//v在栈中,修改low[u]
                low[u] = dfn[v];
        }
    
        if (dfn[u] == low[u])//u为该强连通分量中遍历所成树的根
        {
            ++scc;
            do
            {
                v = S[--top];//栈中所有到u的点都属于该强连通分量,退栈
                vis[v] = false;
                belong[v] = scc;
            } while (u != v);
        }
    
    }
    
    int solve(int i)
    {
        scc = top = cnt = 0;
        memset(dfn, 0, sizeof(dfn));
        memset(vis, false, sizeof(vis));
        dfn[i] = low[i]= inf;
        for (int u=1; u<=n; ++u)
            if (dfn[u] == 0)
                tarjan(u);
        return scc;
    }
    
    void count_deg()
    {
        memset(indeg, 0, sizeof(indeg));
        memset(outdeg, 0, sizeof(outdeg));
        for (int u=1; u<=n; ++u)
            for (int i=head[u]; i!=-1; i=Edge[i].next)
            {
                int v = Edge[i].to;
                if (belong[u] != belong[v])
                {
                    indeg[belong[v]]++;
                    outdeg[belong[u]]++;
                }
            }
    }
    
    int main()
    {
        int u, v, i;
        while (~scanf("%d%d", &n, &m))
        {
        	memset(cut, false, sizeof(cut));
            e = 0;
            memset(head, -1, sizeof(head));
            for(int i=0; i<m; i++){
            	cin >> u >> v; 
                addedge(u, v);
                addedge(v, u);
            }
            int l, r;
            
            cin >> l >> r;
            
    		int cutn = 0;
            //将i点去除后,判断l与r是否在同一个树里...若不在则为割点 
    		for(int i=1; i<=n; i++){
            
    			solve(i);
    			
    			
            if (scc == 1)
            	;
            	//cout << -1 << endl;
               // printf("1\n0\n");
            else
            {
            	if(belong[l] != belong[r])
            	 {
            	 	//因为自身去除后.....也属于一种情况... 
            	 	if(i == l || i == r)
            	 		continue;
           // 	 	cout << i << endl;
    			 	cutn++;
    			 }
             /*   count_deg();
                int inc = 0, outc = 0;
                for (int i=1; i<=scc; ++i)
                {
                    if (indeg[i] == 0)
                        inc++;
                    if (outdeg[i] == 0)
                        outc++;
                }
                printf("%d\n%d\n", inc, (inc > outc ? inc : outc));*/
            }
            
        }
        if(cutn == 0)
        	cout << -1 << endl; 
        else
    		cout << cutn <<endl;
        }
        return 0;
    }
    

     

    展开全文
  • tarjan求强联通分量

    2020-05-20 15:48:28
    题意:出度为0的强联通分量,从小到大输出点的 id 思路:对每一个强联通分量染色。统计每个强联通分量的出度。把出度为 0 的强联通分量放入 ans 中 #include <cstdio> #include <vector> #include <...

    DAG定理

    • 有向无环图中唯一出度为 0 的点,任意点都可以到达
    • 有点无环图中任何入度不为0的点,一定可以从某个入度为 0 的点到达

    tarjan求强联通分量

    算法流程

    • dfn[u]dfn[u]: 深度优先搜索遍历时结点 uu 被搜索的次序
    • low[u]low[u]:以 uu 为根的子树中所有结点的 dfndfn 的最小值

    参考链接

    练习题

    P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

    链接:https://www.luogu.com.cn/problem/P2341

    题意:给定一个有向图,问所以点都可以到达的点有多少个。

    思路:先将有向图变成DAG,然后根据定理 1 ,统计出度为 0 的点是否唯一,如果唯一,那么答案就是这个点代表的scc的大小。否则答案为0。

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e4+10,maxm=5e4+10;
    
    vector<int> e[maxn];
    int dfn[maxn],times,low[maxn];
    stack<int> sta;
    bool insta[maxn];
    int scc[maxn],scnt,num[maxn];
    
    int deg[maxn];//出度 
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta.push(u);insta[u]=1;
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		scnt++;
    		while(1)
    		{
    			int v=sta.top();sta.pop();insta[v]=0;
    			scc[v]=scnt;num[scnt]++;
    			if(v==u) break;
    		}
    	}
    }
    int n,m;
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;++i)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		e[u].push_back(v);
    	}
    	for(int i=1;i<=n;++i)
    		if(!dfn[i]) dfs(i);
    	for(int u=1;u<=n;++u)
    	{
    		for(auto v: e[u])
    		{
    			if(scc[v]!=scc[u])
    				deg[scc[u]]++;
    		}
    	}
    	int cnt=0,ans=0;
    	for(int i=1;i<=scnt;++i)
    		if(deg[i]==0) cnt++,ans=num[i];
    	if(cnt==1) printf("%d\n",ans);
    	else puts("0");
    	return 0;
    }
    

    367. 学校网络

    链接:https://www.acwing.com/problem/content/description/369/

    题意:将有向图转化为DAG后,其实就是求加几条边才能使得整个图变成一个scc

    思路

    • 如果DAG只有一个强联通分量(一个点),那么不需要加边,答案为 0
    • 否则,求DAG入度为 0 的点有多少个,出度为 0 的点有多少个,两者取最大值就是答案。
    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=100+10,maxm=5e4+10;
    
    vector<int> e[maxn];
    int dfn[maxn],times,low[maxn];
    stack<int> sta;
    bool insta[maxn];
    int scc[maxn],scnt,num[maxn];
    
    int in[maxn],out[maxn];
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta.push(u);insta[u]=1;
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		scnt++;
    		while(1)
    		{
    			int v=sta.top();sta.pop();insta[v]=0;
    			scc[v]=scnt;num[scnt]++;
    			if(v==u) break;
    		}
    	}
    }
    int n,m;
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    	{
    		int v;
    		while(scanf("%d",&v)&&v)
    			e[i].push_back(v);
    	}
    	for(int i=1;i<=n;++i)
    		if(!dfn[i]) dfs(i);
    	for(int u=1;u<=n;++u)
    	{
    		for(auto v: e[u])
    		{
    			if(scc[v]!=scc[u])
    				in[scc[v]]++,out[scc[u]]++;
    		}
    	}
    	int cnt1=0,cnt2=0;
    	for(int i=1;i<=scnt;++i)
    	{
    		if(in[i]==0) cnt1++;
    		if(out[i]==0) cnt2++;
    	}
    	int ans2;
    	if(scnt==1) ans2=0;
    	else ans2=max(cnt1,cnt2);
    	printf("%d\n%d\n",cnt1,ans2);
    	return 0;
    }
    

    P3387 【模板】缩点 (tarjan缩点 + DP)

    链接:https://www.luogu.com.cn/problem/P3387

    题意:给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

    思路:缩点之后求DAG上最长链

    • 设 dp[u] = 从u出发路径的最大权值和
    • dp[u] = max{dp[v]} + sz[u], u->v有向边, sz[u] = scc权值和
    • Tarjan算法求出scc的顺序就是拓扑序倒序(拓扑序靠后的scc先出栈) 找到一个新的scc,就对其出边做DP即可。
    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e4+10,maxm=5e4+10;
    
    vector<int> e[maxn];
    int dfn[maxn],times,low[maxn];
    stack<int> sta;
    bool insta[maxn];
    int scc[maxn],scnt,num[maxn];
    
    int n,m,w[maxn],dp[maxn],ans;
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta.push(u);insta[u]=1;
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		scnt++;
    		int res=0;
    		while(1)
    		{
    			int v=sta.top();sta.pop();insta[v]=0;
    			scc[v]=scnt;num[scnt]+=w[v];
    			for(auto v1: e[v])
    				if(scc[v1]&&scc[v1]!=scnt) res=max(res,dp[scc[v1]]);
    			if(v==u) break;
    		}
    		dp[scnt]=res+num[scnt];
    		ans=max(ans,dp[scnt]);
    	}
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
    	for(int i=1;i<=m;++i)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		e[u].push_back(v);
    	}
    	for(int i=1;i<=n;++i)
    		if(!dfn[i]) dfs(i);
    	printf("%d\n",ans);
    	return 0;
    }
    

    也可以遍历所有的边之后重新建图,正向拓扑排序然后dp。这里需要给 dp 赋初值

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e4+10;
    
    vector<int> e[maxn],e2[maxn];
    int n,m,w[maxn];
    
    int dfn[maxn],low[maxn],times;
    int sta[maxn],insta[maxn],top;
    int scc[maxn],scnt,num[maxn];
    
    int dp[maxn],ans,in[maxn];
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta[++top]=u;insta[u]=1;
    	
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		++scnt;
    		int res=0;
    		while(1)
    		{
    			int v=sta[top--];insta[v]=0;
    			scc[v]=scnt;num[scnt]+=w[v];
    			if(v==u) break;
    		}
    	}
    }
    
    void topo()
    {
    	queue<int> q;
    	for(int i=1;i<=scnt;++i)
    		if(in[i]==0) q.push(i),dp[i]=num[i];
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		for(auto v: e2[u])
    		{
    			dp[v]=max(dp[v],dp[u]+num[v]);
    			in[v]--;
    			if(in[v]==0) q.push(v);
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
    	for(int i=1;i<=m;++i)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		e[u].push_back(v);
    	} 
    	ans=top=scnt=times=0;
    	for(int i=1;i<=n;++i)
    		if(!dfn[i]) dfs(i);
    	for(int u=1;u<=n;++u)
    		for(auto v: e[u])
    			if(scc[v]!=scc[u]) 
    				e2[scc[u]].push_back(scc[v]),in[scc[v]]++;
    	topo();
    	for(int i=1;i<=scnt;++i) ans=max(ans,dp[i]);
    	printf("%d\n",ans);
    	return 0;
    }
    

    P1073 最优贸易

    题意:给定 n 个城市的水晶球价格,有 m 条道路,问从 1 到 n 的的路径中,买入一次和卖出一次能获取的最大利益是多少?同一个城市可以经过多次,不需要经过所有城市。

    思路:缩点+ DP。

    • dp[scc[u]]dp[scc[u]] 表示从 scc[u]scc[u] 到达 scc[n]scc[n] 中最大的价格。
    • 用一个 f[scc[u]]f[scc[u]] 数组来表示 scc[u]scc[u] 是否能够到达 scc[n]scc[n],如果不连通那么 dp[scc[u]]=0dp[scc[u]] = 0,这样做的目的是,使得这个 scc 不能更新答案。
    • 同时枚举每个城市作为最小值。
    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e5+10;
    
    int n,m,w[maxn],f[maxn],dp[maxn],ans=0;
    vector<int> e[maxn];
    
    int dfn[maxn],low[maxn],times=0;
    int sta[maxn],insta[maxn],top=0;
    int scc[maxn],scnt=0,sz[maxn];
    
    void dfs(int u)
    {
        dfn[u]=low[u]=++times;
        sta[++top]=u;
        insta[u]=1;
        for(auto v: e[u])
        {
            if(!dfn[v])
            {
                dfs(v);
                low[u]=min(low[u],low[v]);
            }
            else if(insta[v]) low[u]=min(low[u],dfn[v]);
        }
        if(dfn[u]==low[u])
        {
            scnt++;
            int minn=2e9,maxx=0;
            while(1)
            {
                int v=sta[top--];
                insta[v]=0;
                scc[v]=scnt;
                sz[scnt]+=w[v];
                if(v==n) f[scnt]=1;
    
                minn=min(minn,w[v]);
                dp[scnt]=max(dp[scnt],w[v]);
                for(auto v1: e[v])
                {
                    if(scc[v1]&&scc[v1]!=scc[v])
                    {
                        f[scc[v]]|=f[scc[v1]];
                        if(f[scc[v1]]) maxx=max(maxx,dp[scc[v1]]);
                    }
                }
                if(v==u) break;
            }
            if(f[scnt]) dp[scnt]=max(dp[scnt],maxx);
            else dp[scnt]=0;//没有路到n
            ans=max(ans,dp[scnt]-minn);
        }
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; ++i) scanf("%d",&w[i]);
        for(int i=1; i<=m; ++i)
        {
            int u,v,x;
            scanf("%d%d%d",&u,&v,&x);
            e[u].push_back(v);
            if(x==2) e[v].push_back(u);
        }
        dfs(1);
        printf("%d\n",ans);
        return 0;
    }
    

    P3275 [SCOI2011]糖果 (求最小值,解最长路)

    链接:https://www.luogu.com.cn/problem/P3275

    题意:给一些关于 xix_i 的不等式,求 xi\sum x_i 的最小值

    思路

    • 求最小值,需要反向建图跑最长路,出现正环时无解。将条件 xixjdx_i- x_j\le d 变为 xidxjx_i-d\le x_j,即从 iijj 连一条权值为 d-d 的边

    • 隐含条件: xi1x_i \ge 1,可以设一个超级源点 s,设 dis[s]=1dis[s]=1,然后 ss 向每个 xix_i 连一条权值为 00 的边

    • 差分约束并不是正解,这份代码会 T

    • 正解是缩点成DAG之后,正向建图并在topo 排序时 dp。设 dis[i]dis[i] 表示从 0 到 i 的最长路
      dis[v]=max(dis[v],dis[u]+w(u,v)),(uv)dis[v]= max(dis[v],dis[u]+w(u,v)) ,(u为v的入边)

    • 最后统计答案时,每一个 scc 都是一个 0 环,也就是上面的每一个孩子分得的糖果是相同的。因此: ans=i=1scntdis[i]×sz[i]ans=\sum_{i=1}^{scnt} dis[i]\times sz[i]

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define ll long long
    using namespace std;
    const int maxn=1e5+10;
    
    int n,m;
    vector<pair<int,int> > e[maxn],e2[maxn];
    
    int dfn[maxn],low[maxn],times=0;
    int sta[maxn],insta[maxn],top=0;
    int scc[maxn],sz[maxn],scnt=0;
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta[++top]=u;insta[u]=1;
    	for(auto x: e[u])
    	{
    		int v=x.fi,w=x.se;
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		scnt++;
    		while(1)
    		{
    			int v=sta[top--];insta[v]=0;
    			scc[v]=scnt;sz[scnt]++;
    			if(v==u) break;
    		}
    	}
    }
    
    int in[maxn],dis[maxn];
    
    void topo()
    {
    	queue<int> q;
    	for(int i=1;i<=scnt;++i)
    		if(in[i]==0) q.push(i);
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		for(auto x: e2[u])
    		{
    			int v=x.fi,w=x.se;
    			dis[v]=max(dis[v],dis[u]+w);
    			in[v]--;
    			if(in[v]==0) q.push(v);
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;++i)
    	{
    		int x,u,v;
    		scanf("%d%d%d",&x,&u,&v);
    		if(x==1) e[u].push_back({v,0}),e[v].push_back({u,0});
    		else if(x==2) e[u].push_back({v,1});
    		else if(x==3) e[v].push_back({u,0});
    		else if(x==4) e[v].push_back({u,1});
    		else if(x==5) e[u].push_back({v,0});
    	}
    	for(int i=1;i<=n;++i) e[0].push_back({i,1});
    	dfs(0);
    	for(int i=0;i<=n;++i)
    	{
    		for(auto x: e[i])
    		{
    			int v=x.fi,w=x.se;
    			if(scc[v]==scc[i]&&w==1)
    			{
    				puts("-1");
    				return 0;
    			}
    			else if(scc[v]!=scc[i]) e2[scc[i]].push_back({scc[v],w}),in[scc[v]]++;
    		}
    	}
    	topo();
    	ll ans=0;
    	for(int i=1;i<=scnt;++i)
    		ans+=dis[i]*sz[i];
    	printf("%lld\n",ans);
    	return 0;
    }
    

    401. 从u到v还是从v到u?

    链接:https://www.acwing.com/problem/content/description/403/

    题意:给定一个 n 个点 m 条边的有向图,现在要求图中任意两点u和v,均可满足u能通往v或v能通往u,请你判断要求是否能够成立。

    思路:答案为YES的充要条件是 拓扑序唯一
    由于有重边,采用topo排序进行判断,看q.size()是否始终<=1即可。

    • 如果存在两种拓扑序:比如: a -> b ,b -> a 。意思是说,a、b 同时存在与队列中,即入度同时为 0 ,此时 a、b互相不可达。
    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define ll long long
    using namespace std;
    const int maxn=1000+10;
    
    int t,n,m;
    vector<int> e[maxn],e2[maxn];
    int in[maxn];
    
    int dfn[maxn],low[maxn],times=0;
    int sta[maxn],insta[maxn],top=0;
    int scc[maxn],scnt=0;
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta[++top]=u;insta[u]=1;
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		scnt++;
    		while(1)
    		{
    			int v=sta[top--];insta[v]=0;
    			scc[v]=scnt;
    			if(v==u) break;
    		}
    	}
    }
    
    int topo()
    {
    	queue<int> q;
    	for(int i=1;i<=scnt;++i)
    		if(in[i]==0) q.push(i);
    	while(!q.empty())
    	{
    		if(q.size()>1) return 0; 
    		int u=q.front();q.pop();
    		for(auto v: e2[u])
    		{
    			in[v]--;
    			if(in[v]==0) q.push(v);
    		}
    	}
    	return 1;
    }
    
    int main()
    {
    	scanf("%d",&t);
    	while(t--)
    	{
    		scanf("%d%d",&n,&m);
    		for(int i=1;i<=n;++i)
    		{
    			dfn[i]=low[i]=in[i]=0;
    			e[i].clear();
    			e2[i].clear();
    		}
    		top=scnt=times=0;	
    		for(int i=1;i<=m;++i)
    		{
    			int u,v;
    			scanf("%d%d",&u,&v);
    			e[u].push_back(v);
    		}
    		for(int i=1;i<=n;++i)
    			if(!dfn[i]) dfs(i);
    		for(int u=1;u<=n;++u)
    			for(auto v: e[u])
    				if(scc[v]!=scc[u]) 
    					e2[scc[u]].push_back(scc[v]),in[scc[v]]++;
    		puts(topo()?"Yes":"No");
    	}
    	return 0;
    }
    

    402. 杀人游戏

    链接:https://www.acwing.com/problem/content/description/404/

    题意: n 个人中存在一个杀手,询问一个人时,如果是平民,他会告诉你他知道的平民和杀手是谁,如果是杀手那么他会杀死警察。假设每个人是杀手的概率相同,问警察找到凶手的概率是多少?

    思路:缩点后,找入度为 0 的点的数量。同时特判一种特殊情况,即可以直接排除的情况,其他所有点都不是杀手,可以推得最后一个人是杀手。

    • 这种情况:要么是孤立的一个点,或者是这个点的所有出边,都可以被其他的路径访问。即这个点的出边的入度大于等于2。
    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1e5+10;
    
    int n,m;
    vector<int> e[maxn],e2[maxn];
    int in[maxn],out[maxn];
    
    int dfn[maxn],low[maxn],times=0;
    int sta[maxn],insta[maxn],top=0;
    int scc[maxn],sz[maxn],scnt=0;
    
    void dfs(int u)
    {
    	dfn[u]=low[u]=++times;
    	sta[++top]=u;insta[u]=1;
    	for(auto v: e[u])
    	{
    		if(!dfn[v])
    		{
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(insta[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		scnt++;
    		while(1)
    		{
    			int v=sta[top--];insta[v]=0;
    			scc[v]=scnt;sz[scnt]++;
    			if(v==u) break;
    		}
    	}
    }
    
    bool check(int u)
    {
    	if(sz[u]!=1) return 0;
    	if(out[u]==0) return 1;
    	for(auto v: e2[u])
    		if(in[v]<=1) return 0; 
    	return 1;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;++i)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		e[u].push_back(v);
    	}
    	for(int i=1;i<=n;++i)
    		if(!dfn[i]) dfs(i);
    	for(int u=1;u<=n;++u)
    	{
    		for(auto v: e[u])
    			if(scc[v]!=scc[u])
    			{
    				in[scc[v]]++;out[scc[u]]++;
    				e2[scc[u]].push_back(scc[v]);
    			}
    	}
    	int cnt=0,f=0;
    	for(int i=1;i<=scnt;++i)
    	{
    		if(in[i]==0)
    		{
    			cnt++;
    			if(f==0&&check(i)) f=1;
    		}
    	}
    	if(f) cnt--;
    	printf("%.6lf\n",1-cnt*1.0/n);
    	return 0;
    }
    

    The Bottom of a Graph HDU - 2553 (tarjan 求强联通分量 + 缩点)

    题意:求出度为0的强联通分量,从小到大输出点的 id
    思路:对每一个强联通分量染色。统计每个强联通分量的出度。把出度为 0 的强联通分量放入 ans 中

    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #define ll long long
    using namespace std;
    const int maxn=50000+5,inf=0x3f3f3f3f;
    
    int n,m;
    int head[maxn],cnt;
    
    struct Edge
    {
        int nxt,to;
    }edges[maxn<<1];
    
    void add(int u,int v)
    {
        edges[++cnt].to=v;
        edges[cnt].nxt=head[u];
        head[u]=cnt;
    }
    
    int low[maxn],dfn[maxn],inStack[maxn],id;
    int sta[maxn],top;
    int tot,color[maxn],out[maxn];
    
    void init()
    {
    	cnt=0;
    	memset(head,-1,sizeof(head));
    
    	id=0;
    	memset(low,0,sizeof(low));
    	memset(dfn,0,sizeof(dfn));
    	memset(inStack,0,sizeof(inStack));
    
    	tot=0,top=0;
    	memset(color,0,sizeof(color));
    	memset(out,0,sizeof(out));
    }
    
    void tarjan(int u)
    {
        low[u]=dfn[u]=++id;
        sta[++top]=u,inStack[u]=true;
        for(int i=head[u];i!=-1;i=edges[i].nxt)
        {
            int v=edges[i].to;
            if(!dfn[v])//没有访问过
                tarjan(v),low[u]=min(low[u],low[v]);
            else if(inStack[v])//已经访问过,但是还未包含在其他联通分量中
                low[u]=min(low[u],dfn[v]);
            //已经访问过,且已经包含在其他联通分量之中,所以不再栈内
        }
        if(low[u]==dfn[u])
        {
            tot++;
            do
            {
            	color[sta[top]]=tot;
            	inStack[sta[top]]=false;
            }while(sta[top--]!=u);
        }
    }
    
    int main()
    {
        while(scanf("%d",&n)&&n)
        {
        	init();
            scanf("%d",&m);
            for(int i=1;i<=m;++i)
            {
                int u,v;
                scanf("%d%d",&u,&v);
                add(u,v);
            }
            for(int i=1;i<=n;++i)
                if(!dfn[i])
                    tarjan(i);
    
            for(int u=1;u<=n;++u)
            {
                for(int i=head[u];i!=-1;i=edges[i].nxt)
                {
                    int v=edges[i].to;
                    if(color[u]!=color[v])
                        out[color[u]]++;
                }
            }
            vector<int> ans;
            for(int i=1;i<=tot;++i)
            {
                if(out[i]>0) continue;
                for(int j=1;j<=n;++j)
                    if(color[j]==i)
                        ans.push_back(j);
            }
            sort(ans.begin(),ans.end());
            for(int i=0;i<ans.size();++i)
                printf("%d%c",ans[i],i==ans.size()-1?'\n':' ');
        }
        return 0;
    }
    
    展开全文
  • 昨天学到了一个新的算法tarjan算法,感觉最近都...这个tarjan算法用来求强联通分量,在网上看了几篇blog,然后做了一个题目,感觉这个算法很nice啊。。。 如果没有学这个算法, 我肯定会想直接dfs吧orz... dfs看看...

    昨天学到了一个新的算法tarjan算法,感觉最近都没有怎么学习了。。。(最近有个感悟啊,就是学习一定的通过实践来进步的。 现在才明白为什么高中的时候老师强调一定要刷题,当然刷完题目之后的总结也非常地重要!

    这个tarjan算法用来求强联通分量,在网上看了几篇blog,然后做了一个题目,感觉这个算法很nice啊。。。

    如果没有学这个算法, 我肯定会想直接dfs吧orz... dfs看看是不是每个点能到达连通分量的其他点,好像这样非常麻烦啊,还要记录这个点从哪里来的。。。这样一想,好像直接dfs我做不来啊。。。(orz 太菜了。

    那就看看tarjan打野的算法吧。。。

    这个算法用到了两个数组(low 和 dfn数组)和 数据结构栈 还有一个标记数组stk[]用来记录一个点是否在栈中,个人认为这两个数组才是这个算法的关键之处! low用来记录当前连通分量的子树的下标(子树的下标最小,因为我们也是从下标小的点开始dfs 的),dfn用来记录当前这个点被访问的时间,所以在后面的操作中,我们不要去修改dfn的值(因为一个点的被访问时间的确定的啊),我们要修改的是low的值。

    算法的步骤大概如下:

    step1:初始化low,dfn, low[u]=dfn[u]=++time;

    step2:将u压栈并且标记为在栈中 stk[u]=1;

    step3:一个for循环找与u相邻的点v,这里分两个情况 (这里就是dfs的模拟)

      1)如果v未被访问,那么就去dfs(v),如果存在环的话,会导致 low[v]被更新为子树的根的dfn值,所以dfs结束的时候,我们更新一下low[u]=min(low[v], low[u]),有人会问了,如果没有环呢?没有环就更好办了,没有环的话,v是从u搜索过来的 low[v]的初始值大于low[v]的初始值,所以low[u]=min(low[u], low[v])并不变。

      2)如果v已经被访问过,就看看它在不在当前的栈中,如果在,说明v和u同属于一棵子树(那我们更新一下low[v]=min(dfn[u], low[v]) )

    step4:如果一个点的邻边都已经搜索完了,那就从step3跳出来了,这个时候判断一个这个点u的low[u]和dfn[u]是否相等,相等的话说明它是一个强连通分量的根,可以证明一下:如果它不是根的话,那么u的low在step3就已经被更新成了根的dfn,而根的dfn值肯定比low[u]更小的,所以low[u]肯定被更新了且不等于dfn[u]。当low[u]和dfn[u]相等的时候,我们就把压在栈中的同属于一个连通分量的点全部pop,并设stk[u]=0;(表示当前元素已经不在栈中了)。。。因为我们是从节点下标小的点开始dfs的,所以一个联通分量的根节点肯定是节点小下标的点,这样就可以保证pop操作能成功将所有联通分量的点pop出来!

    转载于:https://www.cnblogs.com/ledoc/p/6661709.html

    展开全文
  • 如果两个顶点可以相互通达,则称两个顶点连通(strongly connected)。如果有向图G的每两个顶点都连通,称G是一个连通图。连通图有向图的极大连通子图,称为连通分量(strongly ...Tarjan算法是用来...
  • tanjan算法求强联通分量

    千次阅读 2017-02-27 19:37:55
    图论问题定义: 有向图连通分量: 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点连通(strongly connected)。...非连通图有向图的极大连通子图,成为连通分量(strongly co
  • tarjan这个算法在OI的图论题中很常用它在O(n+m) 的时间内可以出所有强联通分量,然后可以将每一个强联通分量(通常是一个环)缩成一个点。 然后就是一道模板题: 洛谷 P2863 [USACO06JAN]牛的舞会The Cow Prom ...
  • 1 void tarjan(int u) 2 { 3 dfn[u]=low[u]=++dfs_clock; 4 stack_push(u); 5 6 for (int c=head[u];c;c=nxt[c]) 7 { 8 int v=to[c]; 9 if (!dfn[v])...
  • 什么是强联通分量 在一个有向图中: 1.如果两个点可以通过有向边互相联通,那么称这两个点强联通; 2.如果图中任意两个点都强联通,那么称这个有向图为强连通图(一个点也是强连通图); 3.一个非强联通图中的极...
  • 因为这样就不会执行出栈操作,那么这时既然v点还没搜完,现在我们还在搜s点,那么也就是说s点一定是从v那里搜过来的,即s的时间戳比v的大,而这时s又搜到了v,也就说出现强联通关系了,所以更新s的LOW(如果LOW已经...
  • 还是没懂Tarjan算法的原理。...说到以Tarjan命名的算法,我们经常提到的有3个,其中就包括本文所介绍的求强连通分量的Tarjan算法。而提出此算法的普林斯顿大学的Robert E Tarjan教授也是1986年的图灵奖获得者。 ...
  • POJ 2186 Popular Cows 强联通分量 题目链接: POJ 2186 Popular Cows  ...思路:先强联通分量,缩点,强连通分量内所有的点都是相互连通的,然后所有的缩点构成一个DAG(有向无环图),如果图中要存在顶点满足
  • 先构造一个有向图出来,然后求强连通分量,最后找出那些没有入度的连通,就是问题1的答案。比较出度为0的连通个数和入度为0的连通个数,取最大的,即为问题2的答案。(因为要将该DAG变为一个连通,那么最少...
  • 在算法导论的图论算法里,有一个小节讲的是利用两次DFS和一次转置图来有向图的强联通分量,觉得整个解法很有意思,所以想分享给大家一起瞅瞅。 先修知识: 主要是DFS的运行过程,以及我们将在对某个点完成DFS...
  • HDU6072(Kosaraju求强联通分量+位运算)
  • 有向图求强联通分量

    千次阅读 2015-07-14 17:12:21
    Tarjan算法转自:https://www.byvoid.com/blog/scc-tarjan/有向图连通分量 在有向图G中,如果两个顶点间至少存在一条路径,称...非连通图有向图的极大连通子图,称为连通分量(strongly connected components)。
  • 接下来的问题就是求强联通分量了,前面已经说了在有向图中如果一些点构不成环,那么剩下的不是环上的边都有都是割边,且此时缩点不好缩,且割边不易。 放弃,直接强联通分量方便的多,上面的在有向图上割边缩点...
  • 在利用tarjan算法求强联通时,若(u,v)是回边,则low[u]=min(low[u],dfn[v]),对于这条,可以换成low[u]=min(low[u],low[v]),这里low数组记录的是对于某点,它所能回到的最早的结点。 因为对于v能回到的结点,...
  • N)//N代表点的个数,点的标号从1到N,最后得到的结果在Belong数组中,记录的是改点属于的强联通分量的标号,scc代表强联通分量的个数 { memset(DFN, 0 , sizeof (DFN)); memset(Instack, false , sizeof (Instack...
  • Robert Tarjan,计算机科学家,以LCA、连通分量等算法闻名。他拥有丰富的商业工作经验,1985年开始任教于普林斯顿大学。Tarjan于1986年获得图灵奖。并于1994年当选为ACM院士。 Tarjan其他奖项包括: 奈望林纳奖...
  • 参考代码 ...题意:给一个有向图,问它是不是一个强连通图,如果是输出Yes,否输出No第一道tarjan题,是否为强联通图#include #include #include <map>using namespace std; #define ll long long #d
  • 在图中联通和强联通分量是我们解决非树结构的图连通问题的利器 通过求求图的双联通和强联通分量能把图转化成DAG进行求解; 行走 Description 给出一个有向图,你可以选择从任意点出发走到任意点结束,问...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,782
精华内容 2,312
关键字:

强连通怎么求