洛谷P5022 旅行 题解
前面几个代码都是部分分代码,最后一个才是AC了的,所以最后一个有详细注释
这是提高组真题,233有点欧拉回路的感觉。
题目大意:
一个 连通 图,双向边 ,无重边 , 访问图中所有点并依次记录经过的点的编号。求记录的编号字典序最小。
题目数据分析
题目中有 M 条边,而 M 有两种情况: M = N 或者 M = N- 1 。

现在我们将 M = N的情况叫做情况一 ,M = N - 1叫做情况二
情况一和情况二分析
情况二:我们都知道,一棵生成树有N - 1条边,且要保证连通,那么显而易见,情况二是一棵 生成树 。根据生成树的特性,我们可以得知,情况二 不会形成环。
情况一:情况一,不过是在情况二上多了一条边。但是还是要保证连通、无重边,故情况二是 在一棵生成树上多了一条表——有且只有一个环。
根据题意,可以保证数据 一定 会是以上两种情况。
那么现在我们的问题是如何解决情况一和情况二。
情况二
很简单,一棵生成树,只需要使用爆搜即可。
由于情况二是生成树,所以个人认为DFS更好使用。
那怎么搜?
一颗生成树,是可以以树中任意一个点为 \(Root\) (根节点)。而题目要求了是要字典序最小,显然 以 1 为根节点开始DFS的答案会更优秀
此时,只需要从 \(1\) 到 \(N\) 循环,判断目前循环到的点与当前到达的点是否连通,并再继续DFS即可。
血的教训:由于情况二是遍历一棵树,所以不要回溯!
情况二代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5000 + 10;
int n,m;
bool dis[MAXN][MAXN];
int in[MAXN],ans[MAXN],cnt;
bool b[MAXN];
inline int read(){
int f = 1, x = 0;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return f * x;
}
void dfs(int now){
ans[++cnt] = now;
b[now] = true;
for(int i = 1;i <= n; i++){
if(b[i])continue;
if(!dis[now][i])continue;
b[i] = true;
dfs(i);
}
}
int main(){
n = read(),m = read();
for(int i = 1;i <= m; i++){
int x = read(),y = read();
dis[x][y] = dis[y][x] = true;
}
dfs(1);
for(int i = 1;i <= n; i++){
cout<<ans[i]<<" ";
}
}
情况二只占60分。那么其他40分就是情况一了
题目都要由简入难。情况二解决,情况一就有思路可想=借鉴了。
现在情况一不过是在情况二上面多了一个环。还可以使用DFS么?很明显不行。
由于所有点只能访问一次(退回来的不算),环中必须会有一条边无法访问。
为什么
因为对于一棵树,dfs是每个节点与其父亲连接的边都可以搜到的;
但是现在多了一条边,又要满足题目要求——每个点除了第一次访问和回溯外,不能再次访问
所以在情况二上多加了一条边,那么一定有一条边无法访问到。
所以呢?
我们只需要找到唯一的环,在环内任意枚举一条边,删去那一条边,再运行情况二的DFS,最后取最优解即可
注意:情况一DFS与情况二DFS不同,情况一由于有环,所以要回溯。
那么我们现在的问题就是要找环。并准确知道哪些点在环内。
如何找环?
并查集?很明显不行。它只能判断两个点是否是一个父亲且一个点在不在环内,很麻烦求出准确的环。
Tarjan?太麻烦了......
那么就用——拓扑排序!
并查集、Tarjan、拓扑排序之后都会在本人Blog中介绍
我们拓扑排序删边时,只要标记当前队列的头,拓扑之后那些没被标记过的,也就是没被拓扑排序的、在环内的点。
但是拓扑排序只有入度为0的点才加入队列么?题目说是无向图,一条边两个点入度都++,那不就没有入度为0的点了么?
没关系,我们只需要将入度和出度加起来当做度数来,再判断度数是不是1即可。
为什么?
因为拓扑排序在累计入度的时候,环的入口入度一定为 \(3\) ,一个父亲,另外的是环内的点自带的入度。删去与环连接的那一条边,入口点的入度还是会有大于一,故无法进队处理。
那么情况一和情况二的合集代码如下
先不要提交下面的代码!因为没有前面说的注释,所有后面还有没讲完的
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5000 + 10;
int n,m,cur;
int in[MAXN],ans[MAXN];
int res[MAXN];
vector<int>nei[MAXN];
bool dis[MAXN][MAXN];
struct Line{
int xx,yy;
}line[MAXN];
bool b[MAXN],use[MAXN];
inline int read(){
int f = 1, x = 0;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return f * x;
}
void init(){
n = read(),m = read();
for(int i = 1;i <= m; i++){
int x = read(),y = read();
dis[x][y] = dis[y][x] = true;
nei[x].push_back(y);
nei[y].push_back(x);
in[x]++,in[y]++;
line[i] = (Line){x,y};
}
return;
}
void out(){
for(int i = 1;i <= n; i++){
cout<<ans[i]<<" ";
}
cout<<"\n";
return;
}
void new_ans(){
for(int i = 1;i <= n; i++){
ans[i] = res[i];
}
}
void work() {
if(ans[1] == 0){
new_ans();
//out();
return;
}
for(int i = 1; i <= n; i++) {
if(res[i] == ans[i])
continue;
else if(res[i] < ans[i]) {
new_ans();
//out();
return;
}
//out();
return;
}
}
void Topo(){
queue<int>q;
for(int i = 1;i <= n; i++){
if(in[i] == 1){
q.push(i);
use[i] = true;
}
}
while(!q.empty()){
int tot = q.front();
int len = nei[tot].size();
q.pop();
use[tot] = true;
for(int i = 0;i < len; i++){
int next = nei[tot][i];
in[next]--;
if(in[next] == 1){
q.push(next);
use[next] = true;
}
}
}
}
void dfs_plan_1(int now){
ans[++cur] = now;
b[now] = true;
for(int i = 1;i <= n; i++){
if(b[i])continue;
if(!dis[now][i])continue;
b[i] = true;
dfs_plan_1(i);
}
}
void dfs(int now){
res[++cur] = now;
b[now] = true;
for(int i = 1;i <= n; i++){
if(b[i])continue;
if(!dis[now][i])continue;
b[i] = true;
dfs(i);
b[i] = false;
}
b[now] = false;
}
int main(){
init();
for(int i = 1; i <= n; i++) {
sort(nei[i].begin(), nei[i].end());
}
if(m == n - 1){
dfs_plan_1(1);
out();
return 0;
}
Topo();
for(int i = 1;i <= m; i++){
int x = line[i].xx;
int y = line[i].yy;
if(use[x] || use[y])continue;
dis[x][y] = dis[y][x] = false;
cur = 0;
dfs(1);
work();
dis[x][y] = dis[y][x] = true;
}
out();
return 0;
}
为什么TLE了几个点?
不是叫你别提交么
因为这种做法枚举了所有点,会导致超时,所以我们现在只需要维护每个点和它相邻的边,枚举那些边就好。
那么现在思路非常明确了:

那么其实上面就是一个重要的知识——
基环图
没完,代码下面还有东西
#include<bits/stdc++.h>//万能头
using namespace std;
const int MAXN = 5000 + 10;//定义常量
int n,m,cur;//n、m与题目相对应,cur记录答案累加用途
int in_out[MAXN],ans[MAXN];//ans为答案,in_out为度数(即入读加上出度)
int res[MAXN];//用于临时存目前计划,在更新ans与res判断
vector<int>nei[MAXN];//动态数组用于记录邻居,这里用于优化时间复杂度
bool dis[MAXN][MAXN];//邻接矩阵,用来优化判断两个点的边目前又没有删
struct Line{//记录边的结构体
int xx,yy;//起始点和终止点
}line[MAXN];
bool b[MAXN],use[MAXN];//b用于在DFS时看是否搜过了 ,use记录该店是否在环内
inline int read(){//读入优化
int f = 1, x = 0;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return f * x;
}
void init(){//读入函数
n = read(),m = read();//读入点和边
for(int i = 1;i <= m; i++){//读入边
int x = read(),y = read();
dis[x][y] = dis[y][x] = true;//将x与y的邻接矩阵更新
nei[x].push_back(y);//将y压入x的邻居
nei[y].push_back(x);//将x压入y的邻居
in_out[x]++,in_out[y]++;//将x和y的度数++
line[i] = (Line){x,y};//保存边
}
//for(int i = 1;i <= n; i++){
// for(int j = 1;j <= n; j++){
// cout<<dis[i][j]<<" ";
// }
// cout<<endl;
//}
//调试函数
return;
}
void out(){//输出函数,单独拿出来是用于调试用的
for(int i = 1;i <= n; i++){
cout<<ans[i]<<" "; //循环输出答案
}
cout<<"\n";
return;
}
void new_ans(){//更新答案
for(int i = 1;i <= n; i++){
ans[i] = res[i];
}
}
void work() {//用于判断目前方案与答案哪个更优
if(ans[1] == 0){//如果目前答案为空,直接记录
new_ans();//更新答案
//out();
return;//这里一定要返回
}
for(int i = 1; i <= n; i++) {//逐次比较哪个更优
if(res[i] == ans[i])//如果相同则继续
continue;
else if(res[i] < ans[i]) {//当前方案更优
new_ans();//更新
//out();
return;
}
// out();
return;
}
}
void Topo(){//基环图(与拓扑排序差不多)
queue<int>q;//定义队列
for(int i = 1;i <= n; i++){//将度数为1的雅图队列
if(in_out[i] == 1){
q.push(i);
use[i] = true;//标记不在环中
}
}
while(!q.empty()){//还可以继续做
int tot = q.front();//取队首
int len = nei[tot].size();
q.pop();
use[tot] = true;//标记不在环中
for(int i = 0;i < len; i++){//依次删除度数
int next = nei[tot][i];//下一个点
in_out[next]--;
if(in_out[next] == 1){//压入队列
q.push(next);
use[next] = true;
}
}
}
}
void dfs_plan_1(int now){//用于情况二的DFS,这里不要回溯
ans[++cur] = now;//直接更新答案
b[now] = true;//标记
int len = nei[now].size();
for(int i = 0;i < len; i++){//循环
int next = nei[now][i];
if(b[next])continue;
if(!dis[now][next])continue;
b[next] = true;
dfs_plan_1(next);
}
}
void dfs(int now){//用于情况一的DFS,这里要回溯
res[++cur] = now;//更新当前方案
b[now] = true;
int len = nei[now].size();
for(int i = 0;i < len; i++){
int next = nei[now][i];
if(b[next])continue;
//cout<<now<<" "<<next<<" "<<dis[i][next]<<endl;
if(!dis[now][next])continue;
b[next] = true;
//cout<<next;
dfs(next);
b[next] = false;
}
b[now] = false;
//cout<<"end."<<endl;
}
int main(){
init();//读入
for(int i = 1; i <= n; i++) {
//这里很巧妙,从小到大排列邻居,优化
sort(nei[i].begin(), nei[i].end());
}
if(m == n - 1){//情况二
dfs_plan_1(1);
out();
return 0;
}
//情况一
Topo();//基环图
for(int i = 1;i <= m; i++){//枚举删边
int x = line[i].xx;
int y = line[i].yy;
if(use[x] || use[y])continue;//有一个点不在环中就跳过
//cout<<i<<"line:"<<x<<" "<<y<<" "<<dis[x][y]<<endl;
dis[x][y] = dis[y][x] = false;//删边
cur = 0;
dfs(1);//DFS
work();//更新答案
dis[x][y] = dis[y][x] = true;//恢复边
}
out();//输出
return 0;
}
基环图的处理方法
来都来了,听听吧
基换图如上面说的,就是一颗树多了一条边,形成了唯一的环。处理方法也会像上面说的,Topo排序找环、DFS即可
洛谷P5022 旅行 题解的更多相关文章
- 洛谷 P5022 旅行——题解
发现大部分题解都是O(n^2)的复杂度,这里分享一个O(n)复杂度的方法. 题目传送 首先前60%的情况,图是一棵无根树,只要从1开始DFS,每次贪心走点的编号最小的点就行了.(为什么?因为当走到一个 ...
- 洛谷P5022 旅行 题解 去环/搜索
题目链接:https://www.luogu.org/problem/P5022 这道题目一开始看的时候没有思路,但是看到数据范围里面有一个: \(m = n-1\) 或 \(m = n\) ,一下子 ...
- 洛谷 P5022 旅行
今天换标题格式了,因为感觉原版实在有点别扭…… 还是直接上题板,看完题再讲吧: 对了有个小细节没说,m一定是等于n或者等于n-1的. 这题是2018年提高组的真题哦!被我肝了2天肝出来了,2天……(真 ...
- 洛谷NOIp热身赛题解
洛谷NOIp热身赛题解 A 最大差值 简单树状数组,维护区间和.区间平方和,方差按照给的公式算就行了 #include<bits/stdc++.h> #define il inline # ...
- 洛谷P2827 蚯蚓 题解
洛谷P2827 蚯蚓 题解 题目描述 本题中,我们将用符号 ⌊c⌋ 表示对 c 向下取整. 蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓. 蛐蛐国里现 ...
- 洛谷P1816 忠诚 题解
洛谷P1816 忠诚 题解 题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人 ...
- [POI 2008&洛谷P3467]PLA-Postering 题解(单调栈)
[POI 2008&洛谷P3467]PLA-Postering Description Byteburg市东边的建筑都是以旧结构形式建造的:建筑互相紧挨着,之间没有空间.它们共同形成了一条长长 ...
- [NOI 2020 Online] 入门组T1 文具采购(洛谷 P6188)题解
原题传送门 题目部分:(来自于考试题面,经整理) [题目描述] 小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品: 1.圆规,每个 7 元. 2.笔,每支 4 元. 3.笔记本,每本 ...
- [洛谷P3948]数据结构 题解(差分)
[洛谷P3948]数据结构 Description 最开始的数组每个元素都是0 给出n,opt ,min,max,mod 在int范围内 A: L ,R ,X 表示把[l,R] 这个区间加上X(数组的 ...
随机推荐
- Numpy | 04 数组属性
NumPy 数组的维数称为秩(rank),一维数组的秩为 1,二维数组的秩为 2,以此类推. 在 NumPy中,每一个线性的数组称为是一个轴(axis),也就是维度(dimensions).比如说,二 ...
- JS的ES6的Generator
JS的ES6的Generator 1.Generator函数的概念: ES6提供的解决异步编程的方案之一,现在已经不怎么用了被淘汰了. Generator函数是一个状态机,内部封装了不同状态的数据. ...
- 中国大学生计算机系统与程序设计竞赛 CCF-CCSP-2016 选座( ticket_chooser )
选座( ticket_chooser ) 不会正解,欢迎讨论 //60分 #include<cstdio> #define max(a,b) (a)>(b)?a:b #define ...
- MyBatis框架,增删改查
一.recourses中核心配置文件mybatis-config.xml 二. recourse中jdbc.properties 三.entity实体类 四.Dao层 五.ISmbmsUserDao. ...
- xshell && xftp 下载
链接:https://pan.baidu.com/s/1aLdgOSshytIYhArkB7tghQ 提取码:fqjb
- 【AtCoder】 ARC 098
link C-Attention 题意:一个字符队列,每个位置是\(W\)或\(E\),计算最小的修改数量,使得存在一个位置,它之前的都是\(E\),之后的都是\(F\) #include<bi ...
- Server Tomcat v8.5 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
Server Tomcat v9.0 Server at localhost was unable to start within 45 seconds. If the server requires ...
- AWS研究热点:BMXNet – 基于MXNet的开源二进神经网络实现
http://www.atyun.com/9625.html 最近提出的二进神经网络(BNN)可以通过应用逐位运算替代标准算术运算来大大减少存储器大小和存取率.通过显着提高运行时的效率并降低能耗,让最 ...
- Solidity开发注意
pragma版本:1.版本要高于0.4.24才可以编译:2.高于0.5的版本则不可编译:3.第三位的版本号可以变,留出来用做bug可以修复(如0.4.1的编译器有bug,可在0.4.2修复,现有合约不 ...
- 华为交换机在Telnet登录下自动显示接口信息
因为用console连接交换机,默认是自动显示接口信息的,比如down掉一个接口后,会自动弹出接口被down掉的信息,但是在telnet连接下,默认是不显示这些信息的,需要开启后才可显示. 1.首先开 ...