P1726 上白泽慧音

Posted ck666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P1726 上白泽慧音相关的知识,希望对你有一定的参考价值。

上白泽慧音

2017-09-19


题目描述

在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由N个村庄(编号为1..N)和M条道路组成,道路分为两种一种为单向通行的,一种为双向通行的,分别用1和2来标记。如果存在由村庄A到达村庄B的通路,那么我们认为可以从村庄A到达村庄B,记为(A,B)。当(A,B)和(B,A)同时满足时,我们认为A,B是绝对连通的,记为<A,B>。绝对连通区域是指一个村庄的集合,在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。


输入输出格式

输入格式:

第1行:两个正整数N,M

第2..M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。

输出格式:

第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。

第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。


输入输出样例

输入样例#1:
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1
输出样例#1:
3
1 3 5

说明

对于60%的数据:N <= 200且M <= 10,000

对于100%的数据:N <= 5,000且M <= 50,000


 

强连通分量裸题

技术分享
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=5000+999;
int read(){
    int an=0,f=1;
    char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(0<=ch&&ch<=9){an=an*10+ch-0;ch=getchar();}
    return an*f;
}
vector<int>b[maxn];
bool in[maxn];
int id[maxn],cnt,ans_T,P[maxn],low[maxn];
int ans[maxn],n,m;
stack<int>q;
void add(int x,int y){
    b[x].push_back(y);
}
void tarjan(int x){
    cnt++;
    low[x]=id[x]=cnt;
    q.push(x);in[x]=1;
    for(int i=0;i<b[x].size();i++){
        int v=b[x][i];
        if(!id[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(in[v]){
            low[x]=min(low[x],id[v]);
        }
    }
    if(id[x]==low[x]){
        int cnt1=0;
        while(q.top()!=x){
            cnt1++;
            in[q.top()]=0;
            ans[cnt1]=q.top();
            q.pop();
        }
        cnt1++;
        ans[cnt1]=q.top();
        in[q.top()]=0;
        q.pop();
        if(cnt1>ans_T){
            ans_T=cnt1;
            for(int i=1;i<=cnt1;i++)
            P[i]=ans[i];
            sort(P+1,P+1+cnt1);
        }
    }
}
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++){
        int x,y,z;
        x=read();y=read();z=read();
        add(x,y);
        if(z==2)add(y,x);
    }
    for(int i=1;i<=n;i++)if(!id[i])tarjan(i);
    cout<<ans_T<<endl;
    for(int i=1;i<=ans_T;i++)cout<<P[i]<<" ";
}
老师

by:s_a_b_e_r


Tarjan裸题+1

先安静的跑一遍Tarjan板子,对每一个搜到的SCC进行判断,如果大于当前答案那么更新答案

byvoid ←dalao的Tarjan讲解

 

技术分享
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int N=5009,M=500009;
int n,m,cnt,p[N],ans;
int ind,dfn[N],low[N];
bool in[N];
struct edge{int to,nex;}e[M<<1];
stack<int>s;
vector<int>an,scc;
void add(int u,int v){
    e[++cnt]=(edge){v,p[u]};
    p[u]=cnt;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++ind;
    in[u]=1;s.push(u);
    for(int i=p[u];i;i=e[i].nex)
    {
        int v=e[i].to;
        if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
        else if(in[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        int v=0;
        scc.clear();
        while(v!=u)
        {
            v=s.top();s.pop();
            in[v]=0;
            scc.push_back(v);
        }
        if(scc.size()>an.size())
        {
            ans=scc.size();
            sort(scc.begin(),scc.end());
            an.clear();
            for(int i=0;i<scc.size();++i)
            an.push_back(scc[i]);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int a,b,t;
        scanf("%d%d%d",&a,&b,&t);
        if(t==1)add(a,b);
        else{add(a,b);add(b,a);}
    }
    for(int i=1;i<=n;++i)
    if(!dfn[i])tarjan(i);
    cout<<ans<<endl;
    for(int i=0;i<ans;++i)cout<<an[i]<<" ";
    return 0;
}
teacher☆

 

技术分享
1.算法简介

官方(?)定义——“Tarjan 算法:一种由Robert Tarjan提出的求解有向图强连通分量的算法。”
……什么叫“强连通分量”?
这玩意网上真是怎么说的都有啊,查到了好多东西,稍微整合一下……
在有向图G中,如果两个顶点vi,vj间有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点“强连通”。
如果有向图G的每两个顶点都强连通,那么称G是一个“强连通图”。
如果这个有向图G中的一个子图满足每两个点强连通,那么我们把这个子图称为“强连通子图”。(单独一个点也是)
G中的一个极大强连通子图(使最大的环最大),称为一个“强连通分量”。
(可以理解为环,孤立的点是自环)

//后期:↑上面这段我自己都不知道我自己在说啥……什么时候语文好了改下

tarjan里还有一些其他需要用到的定义:
dfn[i]:在dfs中该节点被搜索的次序(又名时间戳)
low[i]表示i所能直接或间接达到的、dfn值最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结果)
当dfn[i]==low[i]时,以i为根的搜索子树上所有节点是一个强连通分量。


2.算法流程

其实说白了还是dfs。
除此之外要用到栈……指的不是递归中的系统栈,是自己加的栈。

首先定义一些dfs树上的边的概念……
树枝边:指dfs过程中的边
        ——就是构成dfs树的边。
横叉边:连接的两个点没有父子关系的边
        ——遇到横叉边怎么办?
        ——采取无视大法。(当然这是后话
后向边:由子节点指向父节点的边
        ——只有这种边能产生环(即产生强连通子图)。

从起始点开始dfs。
每dfs到一个点u,标记dfn,low赋初值=dfn,把u入栈。
然后,开始枚举从u出发的每条边,检查这条边指向的点v。
如果v没有被访问过的话,说明这条边是树枝边,dfs这个点v(递归),递归结束回溯时更新low[u] = min(low[u], low[v])
如果v还在栈中的话,说明这条边是后向边,更新low[u] = min(low[u], dfn[v])
除此之外的情况,即v已经进过栈并且出了栈的情况,说明这条边是横叉边,既不能向下dfs,也不会产生环,于是采取无视大法OvO
以上处理完后,如果u的dfn值依然等于low值的话,即u点所能指向的最小的点是它本身
只有两种情况能做到这一点
其一,这是孤立的一个点;
其二,形成了一个指回u点的环,所以u及u的dfs子树是一个强连通分量。
这时候就可以把栈中u及u的子树项(如果有的话)全部弹出了。
一开始推荐的那个网页里有详细的算法流程演示,超清楚qwq。
很久以前的小w写过的Tarjan笔记

 

by:wypx


 



以上是关于P1726 上白泽慧音的主要内容,如果未能解决你的问题,请参考以下文章

luogu P1726 上白泽慧音

洛谷P1726 上白泽慧音

P1726 上白泽慧音 tarjan 模板

P1726 上白泽慧音

P1726 上白泽慧音

p1726上白泽慧音