前面几个代码都是部分分代码,最后一个才是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即可

最新文章

  1. HubSpot – 网站开发必备的 jQuery 信息提示库
  2. [Android L]SEAndroid开放设备文件结点权限(读或写)方法(涵盖常用操作:sys/xxx、proc/xxx、SystemProperties)
  3. ADO.Net 增、删、改、查(基本项)
  4. iOS视频录制、压缩导出、取帧等http://www.jianshu.com/p/6f23f608048e
  5. 漫谈刑事辩护 z
  6. 自行架设DNS的操作步骤及相关说明
  7. Android—SDCard数据存取&amp;Environment简介
  8. PADS故障解决
  9. SET QUOTED_IDENTIFIER OFF语句的作用
  10. The Swift Programming Language 中国版
  11. golang []byte转string
  12. Android首选项SharedPreference-android学习之旅(六)
  13. idea springboot jrebel hotreloaded
  14. zabbix 监控第三方网络
  15. mysql中外键的创建与删除
  16. [luogu4551][POJ3764]最长异或路径
  17. 贪吃蛇GamePanel Java实现(二)
  18. Yet another way to manage your NHibernate ISessionFactory
  19. python3爬虫-使用requests爬取起点小说
  20. Swiper轮播图

热门文章

  1. Linux 系统管理——进程和计划任务管理
  2. 洛谷p2330繁忙的都市题解
  3. socket数据传输
  4. libvirt原理
  5. Missing Data Reconstruction in Remote Sensing Image With a Unified Spatial–Temporal–Spectral Deep Convolutional Neural Network(缺失数据补全,时空谱网络)
  6. ImportError: cannot import name &#39;DjangoSuitConfig&#39;
  7. Centos 或者 Redhat修改系统时间
  8. java核心技术卷上学习笔记
  9. Inno setup 判断系统32位还是64位
  10. SpringMvc的 @Valid 拦截到的异常如何抛出