洛谷 P1726:上白泽慧音 ← Tarjan算法

news2024/9/20 21:23:41

【题目来源】
https://www.luogu.com.cn/problem/P1726

【题目描述】
在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。
人间之里由
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。

【输入格式】
第一行共两个正整数 N,M。
第 2 行至第 M+1 行,每行有三个正整数 a,b,t。若
t=1 则表示存在从村庄 a 到 b 的单向道路,若 t=2 表示村庄 a,b 之间存在双向通行的道路。保证每条道路只出现一次。

【输出格式】
第一行输出 1 个整数,表示最大的绝对连通区域包含的村庄个数。
第二行输出若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。

【输入样例】
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1

【输出样例】
3
1 3 5

【说明/提示】
对于 60% 的数据,1≤N≤200,且 0≤M≤10^4;
对于 100% 的数据,
1≤N≤5×10^3,且 0≤M≤5×10^4

【算法分析】
● Tarjan算法简介
Tarjan算法是一个基于深搜(DFS),用于求解图中
强连通分量(SCC)割点/割边问题的算法。
Tarjan算法在搜索的过程中,会形成一棵
搜索树

由上图易知,横向边和前向边都无法构成回路,即不能形成大于一个点的强连通分量。所以,在本题中应用 Tarjan 算法的关键,就是找出重要的后向边,用于求解图中的强连通分量。

● Tarjan算法中两个核心数组
dfn[x]low[x]
dfn[x]:表示一个结点的时间戳。时间戳是在 dfs 的过程中,每个结点第一次被访问的时间顺序。
low[x]:表示一个结点的追溯值。追溯值是一个结点通过后向边能够回到的最早结点的编号,也就是 dfn 的最小值
dfn 可以通过两种方式更新:
(1)若在搜索树上 x 是 y 的父结点,那么 low[x]=min(low[x],low[y])
(2)若是
非树边(横向边、后向边),那么 low[x]=min(low[x],dfn[y])
要注意的是,
在无向图中,儿子到父亲的那条边不处理,要不然 low 值就都是 1 了,没有意义。

● Tarjan算法关于图中强连通分量的
判定条件
与当前点 cur 关联的所有边都被遍历完之后,判断它的 dfn 是否等于 low(即,dfn[cur] == low[cur])?若为 True,则表示搜到了一个强连通分量的根。之后,出栈,直到当前结点 cur 终止。出栈的都是这个强连通分量集合内的结点;若为 False,回溯。

● 本题可考虑使用
优先队列实现按字典序输出结果。
优先队列:
https://blog.csdn.net/qq_19656301/article/details/82490601

#include <bits/stdc++.h>
using namespace std;

priority_queue<int,vector<int>,less<int> > down;
priority_queue<int,vector<int>,greater<int> > up;

int main() {
    int n,x;
    cin>>n;
    while(n--) {
        cin>>x;
        down.push(x);
        up.push(x);
    }

    while(!down.empty()) {
        cout<<down.top()<<" ";
        down.pop();
    }
    cout<<endl;
    while(!up.empty()) {
        cout<<up.top()<<" ";
        up.pop();
    }
    
    return 0;
}

/*
in:
5
6 9 2 7 1

out:
9 7 6 2 1
1 2 6 7 9
*/

● 链式前向星:https://blog.csdn.net/hnjzsyjyj/article/details/139369904
val[idx]:存储编号为 idx 的边的值 
e[idx]:存储编号为 idx 的结点的值
ne[idx]:存储编号为 idx 的结点指向的结点的编号
h[a]:存储头结点 a 指向的结点的编号

【算法代码一】

#include <bits/stdc++.h>
using namespace std;

const int maxn=5e3+5;
vector<int> g[maxn];
stack<int> stk;
int scc[maxn]; //scc[i]:第i个强连通分量的结点数
int cnt_scc;
int dfn[maxn]; //dfn[i]:结点i的时间戳
int low[maxn]; //low[i]:结点i的追溯值。也就是dfn的最小值。
int vis[maxn];
int cnt[maxn];
int ts; //时间戳 timestamp

void tarjan(int u) {
    dfn[u]=low[u]=++ts;
    stk.push(u);
    vis[u]=1;
    for(int t:g[u]) {
        if(dfn[t]==0) {
            tarjan(t);
            low[u]=min(low[u],low[t]);
        } else if(vis[t]) {
            low[u]=min(low[u],low[t]);
        }
    }

    if(low[u]==dfn[u]) {
        cnt_scc++;
        while(stk.top()!=u) {
            int t=stk.top();
            vis[t]=0;
            scc[t]=cnt_scc;
            cnt[cnt_scc]++;
            stk.pop();
        }
        scc[u]=cnt_scc;
        stk.pop();
        vis[u]=0;
        cnt[cnt_scc]++;
    }
}

int main() {
    int n,m;
    cin>>n>>m;
    for(int i=1; i<=m; i++) {
        int x,y,op;
        cin>>x>>y>>op;
        g[x].push_back(y);
        if(op==2) g[y].push_back(x);
    }

    for(int i=1; i<=n; i++) {
        if(!dfn[i]) tarjan(i);
    }

    int t=0, ans=-1;
    for(int i=1; i<=n; i++) {
        if(ans<cnt[scc[i]]) {
            ans=cnt[scc[i]];
            t=scc[i];
        }
    }
    cout<<ans<<endl;

    int cur=0;
    for(int i=1; i<=n; i++) {
        if(scc[i]==t) {
            if(cur++) cout<<" ";
            cout<<i;
        }
    }
    cout<<endl;

    return 0;
}


/*
in:
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1

out:
3
1 3 5
*/


【算法代码二】

#include <bits/stdc++.h>
using namespace std;

const int maxn=5e5+5;
const int inf=0x3f3f3f3f;

int dfn[maxn]; //dfn[i]:结点i的时间戳
int low[maxn]; //low[i]:结点i所能到达的最小时间戳
int lie[maxn]; //lie[i]:结点i位于的强联通分量
int siz[maxn]; //siz[i]:第i个强联通分量的结点数
int stk[maxn]; //模拟栈
int top; //栈顶
int tot; //第tot个强联通分量
int ts; //时间戳 timestamp
int ans=-inf;
int n,m;

int e[maxn<<1],ne[maxn<<1],h[maxn],idx;
void add(int a,int b) {
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void tarjan(int u) {
    dfn[u]=low[u]=++ts;
    stk[++top]=u;
    for(int i=h[u]; ~i; i=ne[i]) {
        int v=e[i];
        if(!dfn[v]) {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        } else if(!lie[v]) low[u]=min(low[u],dfn[v]);
    }

    if(low[u]==dfn[u]) {
        lie[u]=++tot;
        siz[tot]++;
        while(stk[top]!=u) {
            siz[tot]++;
            lie[stk[top]]=tot;
            top--;
        }
        top--;
    }
}

int main() {
    memset(h,-1,sizeof(h));
    cin>>n>>m;
    for(int i=1; i<=m; i++) {
        int x,y,f;
        cin>>x>>y>>f;
        if(f==1) add(x,y);
        if(f==2) add(x,y),add(y,x);
    }

    for(int i=1; i<=n; i++) {
        if(!dfn[i]) tarjan(i);
    }

    for(int i=1; i<=tot; i++) ans=max(ans,siz[i]);
    cout<<ans<<endl;
    for(int i=1; i<=n; i++) {
        if(siz[lie[i]]==ans) {
            int cur=lie[i];
            for(int j=i; j<=n; j++) {
                if(lie[j]==cur) cout<<j<<" ";
            }
            return 0;
        }
    }

    return 0;
}

/*
in:
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1

out:
3
1 3 5
*/




【参考文献】
https://www.cnblogs.com/dgsvygd/p/16579748.html
https://www.cnblogs.com/bloodstalk/p/17432793.html
https://blog.csdn.net/yikeyoucaihua/article/details/135832822
https://www.luogu.com.cn/problem/solution/P1726
https://www.luogu.com.cn/article/6dgk1yk1
https://blog.csdn.net/qq_30277239/article/details/118683637
https://zhuanlan.zhihu.com/p/161537289




 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1840298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

李宏毅2023机器学习作业HW06解析和代码分享

ML2023Spring - HW6 相关信息&#xff1a; 课程主页 课程视频 Sample code HW06 视频 HW06 PDF 个人完整代码分享: GitHub | Gitee | GitCode P.S. HW06 是在 Judgeboi 上提交的&#xff0c;出于学习目的这里会自定义两个度量的函数&#xff0c;不用深究&#xff0c;遵循 Sugge…

JS正则表达式构造函数和正则表达式字面量的区别

背景 笔者在使用正则表达式的过程中&#xff0c;经常看到两种使用方式&#xff0c;比较好奇这两种方式有什么不同。 一种是 正则表达式构造函数&#xff1a;new RegExp(“[xxx]”) 另一种是 正则表达式字面量&#xff1a; /[xxx]/ 于是&#xff0c;就去网上搜了一下…结果看到国…

【H5全行业数据大屏展示】—— 数据大屏分享

数据大屏展示是一种将大量数据以可视化形式展示在屏幕上的方式&#xff0c;可以帮助人们更直观地理解和分析数据。在各行各业中&#xff0c;数据大屏展示已经成为一种流行的工具&#xff0c;被广泛应用于数据分析、决策支持和业务监控等方面。在本文中&#xff0c;将分享一些数…

“论数据访问层设计技术及其应用”写作框架,系统架构设计师

论文真题 在信息系统的开发与建设中&#xff0c;分层设计是一种常见的架构设计方法&#xff0c;区分层次的目的是为了实现“高内聚低耦合”的思想。分层设计能有效简化系统复杂性&#xff0c;使设计结构清晰&#xff0c;便于提高复用能力和产品维护能力。一种常见的层次划分模…

文件系统实验(操作系统)

文件系统实验 【预备知识】 1.文件系统的文件类型 为了便于用户利用终端进行输入和输出&#xff0c;UNIX系统做了专门安排。UNIX系统自动为用户打开3个文件&#xff1a;标准输入、标准输出和标准错误输出文件&#xff0c;文件描述符分别为0、1、2&#xff0c;缺省时&#xff0c…

分班查询,一键发布,老师们都在用的分班查询系统

老师们开学季马上又要到了&#xff0c;回想起了每年埋头苦干&#xff0c;对着一堆堆的学生名单&#xff0c;一个个手动分配班级&#xff0c;再一个个通知家长和学生的日子&#xff0c;那种手忙脚乱&#xff0c;生怕出错的紧张感&#xff0c;是不是还历历在目&#xff1f;每次分…

Instagram Reels API接口——高效获取用户主页Reels视频

一、引言 Instagram作为全球知名的社交媒体平台&#xff0c;近年来推出的Reels功能受到了广大用户的热烈欢迎。Reels以短视频的形式&#xff0c;让用户能够轻松创作和分享有趣、有创意的内容。为了帮助开发者、品牌和分析师更好地利用这一功能&#xff0c;我们推出了一款专注于…

从视频创意到传播策略 | 医药产品TVC新媒体传播方案

作为营销策划人&#xff0c;你一定在寻找能够激发创意灵感、拓展策划视野的实战案例。这份最新传播方案由Unithought精心打造&#xff0c;不仅是一份详尽的策划指南&#xff0c;更是一次深入患者心灵的品牌传播实践。 何策网&#xff0c;每日收录全网方案PPT &#xff01; 方…

手把手教你如何修复填补画图时间序列时出现的空白区域,Python向,Plotly库

填补画图时出现的空白区域&#xff0c;Python向&#xff0c;Plotly库 画图的烦恼美丽的plotly库首选安排时间序列的引索Index接下来我们安排plotly来画图继续修正图的格式 画图的烦恼 大家画时间序列的时候肯定遇到过画图没有软件里来的那么舒服&#xff0c;怎么画都会出现空白…

计算机顶级会议和顶级期刊

顶级会议 国际计算机设计会议&#xff08;ICCD&#xff09;&#xff1a;由国际电气与电子工程师协会&#xff08;IEEE&#xff09;主办&#xff0c;是计算机体系结构领域的国际顶级会议之一&#xff0c;已经成功举办四十余届。 NeurIPS&#xff1a;全称神经信息处理系统大会&a…

机械臂 CoppeliaSim Simulink联合仿真

实现机械臂在CoppeliaSim&#xff08;以前称为V-REP&#xff09;和Simulink上的联合仿真涉及多个步骤&#xff0c;包括环境设置、模型导入、通信配置、控制算法设计和测试调试。 前期准备 安装软件配置工作环境创建和配置CoppeliaSim场景 导入机械臂模型配置机械臂参数在Simuli…

goldfish loss:减少训练数据泄漏,提高大语言模型输出的多样性

LLMs&#xff08;大型语言模型&#xff09;能够记忆并重复它们的训练数据&#xff0c;这可能会带来隐私和版权风险。为了减轻记忆现象&#xff0c;论文作者引入了一种名为"goldfish loss"的微妙修改&#xff0c;在训练过程中&#xff0c;随机抽样的一部分标记被排除在…

阿里云ECS(CentOS/Alibaba Cloud Linux)安装最新 Docker 方法

最近&#xff08;6月份&#xff09;我发现 docker 官方无法正常访问&#xff0c;docker pull 命令也执行失败&#xff0c;用 TZ 也一样&#x1f614;。 以下步骤适用于 CentOS 7/8或Alibaba Cloud Linux 系统。 1. 更新系统包 首先&#xff0c;确保您的ECS实例系统软件包是最…

《Linux运维总结:基于ARM64架构CPU使用docker-compose一键离线部署alertmanager v0.27.0高可用集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面对不同的客户部署业务系统&#xff0…

ansible 模块进阶及变量

yum 模块进阶 - name: install pkgs hosts: webservers tasks: - name: install web pkgs # 此任务通过yum安装三个包 yum: name: httpd,php,php-mysqlnd state: present # 根据功能等&#xff0c;可以将一系列软件放到一个组中&#xff0c;安装软件包组&#xff0c;将会把很…

代码随想录第28天|回溯算法

491. 非递减子序列 思路: 不可以排序, 否则会改变元素的顺序对收获的结果有要求, num.size() > 2, 且 num[i - 1] < num[i]需要进行去重, 不能使用排序后的方法去重每一层可用 unordered_set 去重组合问题, for 遍历需要标记起始位置 bug: 一定要先判断元素是否重复, …

进阶篇06——锁

概述 全局锁 表级锁 表锁 元数据锁 元数据锁是系统自动加的&#xff0c;不需要我们手动执行命令添加。 意向锁 意向锁和元数据锁一样&#xff0c;也是在加行锁的时候自动给表加上相应的意向锁&#xff0c;不需要我们手动添加。 行级锁 行锁 读锁和读锁兼容&#xff0c;写锁…

【决战欧洲杯巅峰】AI模型预测[走地数据]初步准备工作

数据准备 首先&#xff0c;我们需要收集一些与欧洲杯比赛相关的历史数据。这些数据可能包括球队的历史战绩、球员的能力评分、比赛场地信息、历史交锋记录等。这些数据可以从公开来源获取&#xff0c;并进行适当的预处理和清洗。 特征提取 接下来&#xff0c;我们需要从收集…

项目实施经理岗位的工作内容(合集)

项目实施经理岗位的工作内容1 职责&#xff1a; (1)负责协调软件团队对软件产品的研发工作(包括代码开发&#xff0c;测试&#xff0c;部署实施等); (2)引导和解析客户需求&#xff0c;根据产品特点及用户个性化需求制定解决方案&#xff0c;完成客户宣讲等售前技术支持工作; (…

【机器学习】第5章 朴素贝叶斯分类器

一、概念 1.贝叶斯定理&#xff1a; &#xff08;1&#xff09;就是“某个特征”属于“某种东西”的概率&#xff0c;公式就是最下面那个公式。 2.朴素贝叶斯算法概述 &#xff08;1&#xff09;是为数不多的基于概率论的分类算法&#xff0c;即通过考虑特征概率来预测分类。 …