前面几个代码都是部分分代码,最后一个才是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 旅行 题解的更多相关文章

  1. 洛谷 P5022 旅行——题解

    发现大部分题解都是O(n^2)的复杂度,这里分享一个O(n)复杂度的方法. 题目传送 首先前60%的情况,图是一棵无根树,只要从1开始DFS,每次贪心走点的编号最小的点就行了.(为什么?因为当走到一个 ...

  2. 洛谷P5022 旅行 题解 去环/搜索

    题目链接:https://www.luogu.org/problem/P5022 这道题目一开始看的时候没有思路,但是看到数据范围里面有一个: \(m = n-1\) 或 \(m = n\) ,一下子 ...

  3. 洛谷 P5022 旅行

    今天换标题格式了,因为感觉原版实在有点别扭…… 还是直接上题板,看完题再讲吧: 对了有个小细节没说,m一定是等于n或者等于n-1的. 这题是2018年提高组的真题哦!被我肝了2天肝出来了,2天……(真 ...

  4. 洛谷NOIp热身赛题解

    洛谷NOIp热身赛题解 A 最大差值 简单树状数组,维护区间和.区间平方和,方差按照给的公式算就行了 #include<bits/stdc++.h> #define il inline # ...

  5. 洛谷P2827 蚯蚓 题解

    洛谷P2827 蚯蚓 题解 题目描述 本题中,我们将用符号 ⌊c⌋ 表示对 c 向下取整. 蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓. 蛐蛐国里现 ...

  6. 洛谷P1816 忠诚 题解

    洛谷P1816 忠诚 题解 题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人 ...

  7. [POI 2008&洛谷P3467]PLA-Postering 题解(单调栈)

    [POI 2008&洛谷P3467]PLA-Postering Description Byteburg市东边的建筑都是以旧结构形式建造的:建筑互相紧挨着,之间没有空间.它们共同形成了一条长长 ...

  8. [NOI 2020 Online] 入门组T1 文具采购(洛谷 P6188)题解

    原题传送门 题目部分:(来自于考试题面,经整理) [题目描述] 小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品: 1.圆规,每个 7 元. 2.笔,每支 4 元. 3.笔记本,每本 ...

  9. [洛谷P3948]数据结构 题解(差分)

    [洛谷P3948]数据结构 Description 最开始的数组每个元素都是0 给出n,opt ,min,max,mod 在int范围内 A: L ,R ,X 表示把[l,R] 这个区间加上X(数组的 ...

随机推荐

  1. 04-树5 Root of AVL Tree (25 分)

    An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child sub ...

  2. Ubuntu 14.04 安装python3.7

    下载: https://www.python.org/ftp/python/3.7.4/ .tgz文件,解压后,进入该文件夹 编译./configuremakesudo make install 当 ...

  3. Android Studio 屏幕方向以及UI界面状态的保存

    package com.example.orientation; import android.os.Bundle; import android.util.Log; import android.v ...

  4. leetcode 494. 目标数

    题目描述: 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在你有两个符号 + 和 -.对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面. 返回可以 ...

  5. Struts2工作原理和核心文件

    一.Struts2工作原理 如下图: 二.Struts2配置文件 1.web.xml 任何MVC框架都需要与Web应用整合,这就不得不借助于web.xml文件,只有配置了web.xml文件的Servl ...

  6. 019 spring social

    1.原理 2. 3. 4.

  7. deformable conv

    在原feature map上经过卷积生成与原feature map一样w.h大小的feature map,但是channel变为2倍,即2N.2N代表的是每个像素x.y两个方向的偏移量. 这个偏移量生 ...

  8. BicycleGAN: Toward Multimodal Image-to-Image Translation - 1 - 论文学习,成对数据

    Abstract 许多图像到图像的翻译问题是有歧义的,因为一个输入图像可能对应多个可能的输出.在这项工作中,我们的目标是在一个条件生成模型设置中建立可能的输出分布.将模糊度提取到一个低维潜在向量中,在 ...

  9. LeetCode 257. Binary Tree Paths(二叉树根到叶子的全部路径)

    Given a binary tree, return all root-to-leaf paths. Note: A leaf is a node with no children. Example ...

  10. 脚本备份MySQL数据库和binlog日志

    用Mysqldump实现全库备份+binlog的数据还原 首先是为mysql做指定库文件的全库备份 vim mysqlbak.sh #!/bin/bash #定义数据库目录,要能找到mysqldump ...