洛谷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了