洛谷P5022&P5049 旅行(及其数据加强版)
旅行(不是加强版)
加强版
加强版数据范围:
我们注意到
也就是说要么是个树,要么是个基环树
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 旅行(及其数据加强版)的更多相关文章
- 洛谷 P1120 小木棍 [数据加强版]解题报告
P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它 ...
- 洛谷——P1120 小木棍 [数据加强版]
P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍 ...
- 洛谷 P1120 小木棍 [数据加强版]
P1120 小木棍 [数据加强版] 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它 ...
- 洛谷 P2241统计方形(数据加强版) 题解
题目传送门 说是加强版,其实可以把棋盘那道题的代码粘过来(注意要开long long): #include<bits/stdc++.h> using namespace std; ,c; ...
- 洛谷—— P1120 小木棍 [数据加强版]
https://www.luogu.org/problem/show?pid=1120 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接 ...
- 洛谷——P2241 统计方形(数据加强版)
https://www.luogu.org/problem/show?pid=2241 题目背景 1997年普及组第一题 题目描述 有一个n*m方格的棋盘,求其方格包含多少正方形.长方形 输入输出格式 ...
- [洛谷P1120]小木棍 [数据加强版]
题目大意:有一些同样长的木棍,被切割成几段(长$\leqslant$50).给出每段小木棍的长度,找出原始木棍的最小可能长度. 题解:dfs C++ Code: #include<cstdio& ...
- 洛谷P1120 小木棍 [数据加强版]搜索
玄学剪支,正好复习一下搜索 感觉搜索题的套路就是先把整体框架打出来,然后再一步一步优化剪枝 1.从maxv到sumv/2枚举长度(想一想,为什么) 2. 开一个桶,从大到小开始枚举 3. 在搜索中,枚 ...
- P5049 旅行(数据加强版)(基环树)
做法 把环找出来,如果在环上(u,v)两点的时候,u的其他子树都走完了,v上第一个还有除v存在的子树没走完的 祖先,祖先的最小子节点小于v,则回去 Code #include<bits/stdc ...
随机推荐
- gradle 刷新打包的时候报错
java.lang.AbstractMethodError: org.jetbrains.plugins.gradle.tooling.util.ModuleComponentIdentifierIm ...
- Oracle 之 触发器
触发器是特定的事件出现的时候,自动隐式执行代码块,这个过程用户无法控制,用户只能控制触发的事件,触发后的操作,触发过程是自动执行的. 定义触发器: 1.名称 2.触发时间:是在执行事件之前(befor ...
- keepalived启动后报错:(VI_1): received an invalid passwd!的解决办法
一.配置好keepalived.conf文件后,启动keepalived,查看/var/log/message日志报错: [root@push-- sbin]# tail -f /var/log/me ...
- 深入理解JAVA虚拟机 垃圾收集器和内存分配策略
引用计数算法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用的 ...
- Spring:jar包详解
org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spring 2.5.6的时候需要a ...
- 《Head First 软件开发》阅读五
结束开发循环:娟娟细流归大海 几乎完成了任务,而开发循环结束所要面对的问题是用户测试的安排.新的一轮重构和重新设计. 开发循环已经完成,但是还是有很多事情可以去做.系统测试必不可少,但是是由谁来做系统 ...
- PL/SQL(Procedure Language & Structured Query Language)
目前的PL/SQL包括两部分,一部分是数据库引擎部分:另一部分是可嵌入到许多产品(如C语言,JAVA语言等)工具中的独立引擎.可以将这两部分称为:数据库PL/SQL和工具PL/SQL. PL/SQL中 ...
- 解决SonarQube启动时直接挂掉问题
症状:启动SonarQube时,系统启动,但是马上关闭 查看日志,提示ElasticSearch启动有问题ClusterBlockException[blocked by: [FORBIDDEN/12 ...
- mysql——非主键自增
今天遇到一个问题: 要创建一张表,其中我想将ip和date这两列作为一个复合主键,可以通过如下语句修改表结构: alter table tb_name add primary key (字段1,字段2 ...
- css-js-弹出层
HTML: <!-- 弹出层 --> <div class="popwindow" > <div class="pop" id=& ...