旅行(不是加强版)

加强版







加强版数据范围:

我们注意到

也就是说要么是个树,要么是个基环树

60pts

这60分是个树,可以简单的贪心想到每次都走子树中编号最小的那个,并且把1作为根

dfs练手题

还是贴个代码叭

void dfs1(int now,int fa)
{
if(vis[now])return ;
ans1[++t]=now;
vis[now]=1;
vector<int> qwq;//为了不使存储的点被后面的子树覆盖,所以用vector
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
qwq.push_back(v);
}
sort(qwq.begin(),qwq.end());//vector的排序
int qaq=qwq.size();
for(int i=0;i<qaq;i++)
dfs1(qwq[i],now);
}

100pts

大多数的解法

当m==n时,它是一个基环树(即树上挂着一个环的树)

基环树只要删掉环上的一条边,它就是个树了。所以我们可以枚举删掉哪条边。(需要开个\(O_2\))

由于开\(O_2\)会让你的评测记录显得不优雅,我们要考虑考虑怎么不开\(O_2\)过掉这道题。

由于我们要删去环上的边,所以要先找个环,而不是暴力删边再判是否是环上的。这样就可以过去了。

但是博主脑洞清奇所以并没有用这种做法当然也没有代码

当然不是

应某神仙的要求贴上他的代码

oid dfs3(int from,int fa) {//找环代码
vis[from]=1;
for(int i=0;i<a[from].size();i++) {//这里是用vector记录的出边
int to=a[from][i];
if(to==fa)
continue ;
if(vis[to]) {
flag=1;//找到了环
cir1[to]=1;//标记to和from都在环上
cir1[from]=1;
u1[++cnt]=from;//记录在环上的点
v1[cnt]=to;
return ;
}
dfs3(to,from);
if(flag&&cir1[to])
if(cir1[from]) {//判断找到了环的“根”(即以1为根时,最靠近1的在环上的点)
flag=0;
u1[++cnt]=from;
v1[cnt]=to;
return ;
} else {
cir1[from]=1;
u1[++cnt]=from;
v1[cnt]=to;
return ;
}
}
}

全套代码

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> a[5010];
int n,m;
int res[5010],ans[5010],tot;
int cir1[5010];
int u1[10010],v1[10010],cnt;
int vis[5010];
int du,dv;
int flag;
struct Edge {//咱也不造为啥铁锤妹妹要写前向星(虽然后面也没有用到前向星)
int from,to;
}e[5010];
void dfs2(int u,int fa) {//没有环时的dfs
if(vis[u])
return ;
vis[u]=1;
ans[++tot]=u;//记录答案
for(int i=0;i<a[u].size();i++) {
int v=a[u][i];
if(v==fa)
continue ;
dfs2(v,u);
}
}
void dfs1(int u,int fa) {//有环时的dfs
if(vis[u])
return ;
vis[u]=1;
res[++tot]=u;
for(int i=0;i<a[u].size();i++) {
int v=a[u][i];
if(v==fa)
continue ;
if((u==du&&v==dv)||(u==dv&&v==du))//du,dv为枚举删去的边(见主函数)
continue ;
dfs1(v,u);
}
}
void dfs3(int from,int fa) {//找环
vis[from]=1;
for(int i=0;i<a[from].size();i++) {
int to=a[from][i];
if(to==fa)
continue ;
if(vis[to]) {
flag=1;
cir1[to]=1;
cir1[from]=1;
u1[++cnt]=from;
v1[cnt]=to;
return ;
}
dfs3(to,from);
if(flag&&cir1[to])
if(cir1[from]) {
flag=0;
u1[++cnt]=from;
v1[cnt]=to;
return ;
} else {
cir1[from]=1;
u1[++cnt]=from;
v1[cnt]=to;
return ;
}
}
}
int check() {//比较更优方案
for(int i=1;i<=n;i++) {
if(res[i]<ans[i])
return 1;
else if(res[i]>ans[i])
return 0;
}
return 0;
}
void update() {//更新答案
for(int i=1;i<=n;i++) {
ans[i]=res[i];
}
}
int main() {
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++) {
scanf("%d%d",&u,&v);
a[u].push_back(v);//vector选手铁锤妹妹
a[v].push_back(u);
e[i].from=u;
e[i].to=v;
}
for(int i=1;i<=n;i++)
sort(a[i].begin(),a[i].end());
if(m==n) {
dfs3(1,0);//找环
int flag=1;
for(int i=1;i<=cnt;i++) {
du=u1[i];dv=v1[i];//枚举删去环上哪条边
memset(vis,0,sizeof(vis));
tot=0;
dfs1(1,0);
if(tot<n)
continue ;
if(flag) {
update();
flag=0;
}
if(check())
update();
}
for(int i=1;i<=n;i++) {
printf("%d ",ans[i]);
}
} else {
dfs2(1,0);
for(int i=1;i<=n;i++) {
printf("%d ",ans[i]);
}
}
return 0;
}

代码转自铁锤妹妹,注释窝加的

接下来我们谈谈博主清奇的脑洞。

考虑从环入手选择最优解。

先来看最简单的环。



最优解当然是1 2 3 4 5辣。那我们究竟是怎么找出这个顺序的呢?

首先按照60pts的思路,走编号最小的点。走到2。下一个是5,但是如果我们此时回溯到3,走3-->4-->5这条路,所得的字典序会更小。由此可以得到一个贪心思路:在向编号小的点a走的同时,记录下编号较大的点b的编号。当dfs到一个比b编号大且在环上的点时,回溯到b,由b走过去。

我们在记录参数(b的编号)(以下称之为cs)的时候,是在环的“根”处(也就是图中的1节点)记录的,所以要先找个环并且记录环的“根”。

现在把这个环挂到树上。



最优解是1 2 6 4 3 5 7。我们发现在遍历2的子树时一定要走过7.此时就无法回到3然后从3走了。因此我们对cs要有所改变。(cs初始化为inf)

cs更新原则:

如果当前点now是环的“根”root,则cs为它的子树中,在环上且编号较大的那个点

如果当前点在环上但不是root,且cs不是inf。记录它在环上的子树的编号c,找到最大的不在环上的且大于c的子树编号。如果没有,则cs不变,如果有,cs更新。

为什么要cs不是inf才能更新呢?因为如果cs是inf且在环上,说明现在是从编号较大的点走过来的,不需要再判断是否回溯。

找环+对环的dfs:

int rt,er;//rt就是上文中的root,er记录在找环时是否回溯到了root
void huan(int now,int fa)
{
vis[now]++;
if(vis[now]>1)
{hua[now]=1;rt=now;er++;return ;}
if(!head[now])return ;
bool bj=0;//记录是否有子树在环上
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
if(now==rt)break;
huan(v,now);
if(hua[v])bj=1;
}
if(bj)hua[now]=1;//如果有子树在环上,那么now很可能也在环上,特殊情况由下面判断
if(er==2)hua[now]=0;//er==2说明已经回到了root的父亲节点(祖先节点)
if(now==rt)er++;
}
void dfs2(int now,int fa,int cs)
{
if(now>cs)return ;//该回溯了
if(vis[now])return ;
vis[now]=1;
vector<int> qwq;
ans1[++t]=now;//记录答案
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
qwq.push_back(v);
}
sort(qwq.begin(),qwq.end());//依旧是排序子树的顺序
int qaq=qwq.size();
if(now==rt)//在root处开始记录cs
for(int i=qaq-1;i>=0;i--)
if(hua[qwq[i]]){cs=qwq[i];break;}
if(now!=rt&&hua[now]&&cs!=inf)
{
int rwr=inf;
for(int i=qaq-1;i>=0;i--)
if(hua[qwq[i]]){rwr=qwq[i];break;}//rwr记录子树中在环上的点的编号(因为在环上且不是“根”的点有且只有一个子树在环上)
for(int i=qaq-1;i>=0;i--)
if(!hua[qwq[i]]&&qwq[i]>rwr){cs=qwq[i];break;}
}
for(int i=0;i<qaq;i++)
{
if((qwq[i]<cs&&now==rt)||(now!=rt&&hua[qwq[i]])) dfs2(qwq[i],now,cs);//如果是开始走编号较小的点或者说now在环上则要带着cs(由于走较大的点的编号的情况在下面更新了cs,所以这么写也是可以的)
else dfs2(qwq[i],now,inf);//在走较大的点的时候把cs更新掉
}
}

完整版(无注释):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#include<cmath>
using namespace std;
inline int read()
{
char ch=getchar();
int x=0;bool f=0;
while(ch<'0'||ch>'9')
{
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f?-x:x;
}
const int inf=214748364;
int t,n,m,ans1[500009],cnt,head[500009];
int vis[500009];
bool hua[500009];
struct E{
int to,nxt;
}ed[1000009];
void add(int fr,int to)
{
ed[++cnt].to=to;
ed[cnt].nxt=head[fr];
head[fr]=cnt;
}
void dfs1(int now,int fa)
{
if(vis[now])return ;
ans1[++t]=now;
vis[now]=1;
vector<int> qwq;
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
qwq.push_back(v);
}
sort(qwq.begin(),qwq.end());
int qaq=qwq.size();
for(int i=0;i<qaq;i++)
dfs1(qwq[i],now);
}
int rt,er;
void huan(int now,int fa)
{
vis[now]++;
if(vis[now]>1)
{hua[now]=1;rt=now;er++;return ;}
if(!head[now])return ;
bool bj=0;
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
if(now==rt)break;
huan(v,now);
if(hua[v])bj=1;
}
if(bj)hua[now]=1;
if(er==2)hua[now]=0;
if(now==rt)er++;
}
void dfs2(int now,int fa,int cs)
{
if(now>cs)return ;
if(vis[now])return ;
vis[now]=1;
vector<int> qwq;
ans1[++t]=now;
for(int e=head[now];e;e=ed[e].nxt)
{
int v=ed[e].to;
if(v==fa)continue;
qwq.push_back(v);
}
sort(qwq.begin(),qwq.end());
int qaq=qwq.size();
if(now==rt)
for(int i=qaq-1;i>=0;i--)
if(hua[qwq[i]]){cs=qwq[i];break;}
if(now!=rt&&hua[now]&&cs!=inf)
{
int rwr=inf;
for(int i=qaq-1;i>=0;i--)
if(hua[qwq[i]]){rwr=qwq[i];break;}
for(int i=qaq-1;i>=0;i--)
if(!hua[qwq[i]]&&qwq[i]>rwr){cs=qwq[i];break;}
}
for(int i=0;i<qaq;i++)
{
if((qwq[i]<cs&&now==rt)||(now!=rt&&hua[qwq[i]])) dfs2(qwq[i],now,cs);
else dfs2(qwq[i],now,inf);
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int fr=read(),to=read();
add(fr,to);
add(to,fr);
}
if(m==n-1)dfs1(1,0);
else
{
huan(1,0);
memset(vis,0,sizeof(vis));
dfs2(1,0,inf);
}
for(int i=1;i<=t;i++)
printf("%d ",ans1[i]);
memset(vis,0,sizeof(vis));
}

接下来就是愉快的AC了

然后博主发现自己的做法好像比较清奇,于是去交了数据加强版,发现也A了

加强版:把数组改大然后交上就ok了

洛谷P5022&P5049 旅行(及其数据加强版)的更多相关文章

  1. 洛谷 P1120 小木棍 [数据加强版]解题报告

    P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它 ...

  2. 洛谷——P1120 小木棍 [数据加强版]

    P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍 ...

  3. 洛谷 P1120 小木棍 [数据加强版]

    P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它 ...

  4. 洛谷 P2241统计方形(数据加强版) 题解

    题目传送门 说是加强版,其实可以把棋盘那道题的代码粘过来(注意要开long long): #include<bits/stdc++.h> using namespace std; ,c; ...

  5. 洛谷—— P1120 小木棍 [数据加强版]

    https://www.luogu.org/problem/show?pid=1120 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接 ...

  6. 洛谷——P2241 统计方形(数据加强版)

    https://www.luogu.org/problem/show?pid=2241 题目背景 1997年普及组第一题 题目描述 有一个n*m方格的棋盘,求其方格包含多少正方形.长方形 输入输出格式 ...

  7. [洛谷P1120]小木棍 [数据加强版]

    题目大意:有一些同样长的木棍,被切割成几段(长$\leqslant$50).给出每段小木棍的长度,找出原始木棍的最小可能长度. 题解:dfs C++ Code: #include<cstdio& ...

  8. 洛谷P1120 小木棍 [数据加强版]搜索

    玄学剪支,正好复习一下搜索 感觉搜索题的套路就是先把整体框架打出来,然后再一步一步优化剪枝 1.从maxv到sumv/2枚举长度(想一想,为什么) 2. 开一个桶,从大到小开始枚举 3. 在搜索中,枚 ...

  9. P5049 旅行(数据加强版)(基环树)

    做法 把环找出来,如果在环上(u,v)两点的时候,u的其他子树都走完了,v上第一个还有除v存在的子树没走完的 祖先,祖先的最小子节点小于v,则回去 Code #include<bits/stdc ...

随机推荐

  1. 简述在Ubuntu终端打开文件的几种不同方法与区别

    一· 在Ubuntu下,通常用命令行打开文本文件,比如用命令gedit.more.cat.vim.less. gedit:在文本软件下打开文件,可直接修改. more ,cat 和 less :类似, ...

  2. vsftpd一些常用配置

    常用的全局配置 --设置监听的IP地址 listen_asspress=192.168.4.1 --设置监听FTP服务的端口号 listen_port=21 --是否允许下载文件 download_e ...

  3. [转载]Jupyter notebook调试

    原文来自:https://blog.csdn.net/dlhlsc/article/details/84309410 jupyter的调试是通过python自带的pdb库来实现的. 下面讲一下在not ...

  4. 运维nslookup语法

    nslookup 查询域名DNS信息的工具 补充说明 nslookup命令 是常用域名查询工具,就是查DNS信息用的命令. nslookup4有两种工作模式,即“交互模式”和“非交互模式”.在“交互模 ...

  5. laravel5.8 eloquent

    https://learnku.com/docs/laravel/5.8/eloquent/3931 示例1: $flights = App\Flight::all(); foreach ($flig ...

  6. windows设置通过NFS连接到Linux共享数据

    win7下增加了很多有用的功能,只是默认没有开启而已,今天简述下一个WIN7下的NFS功能,通过这个功能,可以让win7共享Linux下面的磁盘分区或者目录数据,这个功能原理只能通过samba或者ft ...

  7. WLAN AutoConfig服务无法开机自动启动

    又到“618”大促销,商家搞活动,买了一只小无线网卡,刚装上,一切正常.重新启动电脑后,发现无线网卡已被禁用!手工启用无线网卡也不能解决.到“计算机管理”-“服务”中将“WLAN Autoconfig ...

  8. BZOJ - 3998 弦论 (后缀自动机)

    #include<cstdio> #include<cstring> #include<queue> using namespace std; typedef lo ...

  9. Spring:jar包详解

    org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spring 2.5.6的时候需要a ...

  10. Vue给子组件传值为空

    在项目中会遇到的情况.给子组件传值. 子组件页面可以把数据展现出来.可在方法中却获取不到 解决方法: 父组件添加判断,让页面执行完.再把值带过去.