题目描述
*注:由于本题是个人A的第一道提高组蓝(luogu)题,所以我将把做这道题的解题思想详细记录下来一遍复习.
普通版P5022
变态 加强版P5049
(目前加强过不了TAT)
小Y是一个爱好旅行的OIer.她来到 X 国,打算将各城市都玩一遍.
小Y了解到, X国的n个城市之间有m条双向道路.每条双向道路连接两个城市.不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路.并且,从任意一个城市出发,通过这些道路都可以到达任意一个其他城市.小 Y 只能通过这些 道路从一个城市前往另一个城市。
小Y的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市.当小Y回到起点时,她可以选择结束这次旅行或继续旅行.需要注意的是,小Y要求在旅行方案中,每个城市都被访问到。
为了让自己的旅行更有意义,小Y决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来.她知道这样会形成一个长度为n的序列.她希望这个序列的字典序最小,你能帮帮她吗? 对于两个长度均为n的序列A和B,当且仅当存在一个正整数x,满足以下条件时, 我们说序列A的字典序小于B。
- 对于任意正整数 1 ≤ i < x {1≤i<x} 1≤i<x,序列A的第i个元素 A i {A_i} Ai,
- 和序列B的第i个元素 B i {B_i} Bi相同。
序列A的第x个元素的值小于序列B的第 x个元素的值。
输入格式
输入文件共m+1行.第一行包含两个整数n,m(m ≤ n),中间用一个空格分隔。
接下来 m 行,每行包含两个整数 u,v (1 ≤ u,v ≤ n),表示编号为u和v的城市之间有一条道路,两个整数之间用一个空格分隔。
输出格式
输出文件包含一行,n个整数,表示字典序最小的序列.相邻两个整数之间用一个空格分隔。
输入输出样例
输入 #1
6 5
1 3
2 3
2 5
3 4
4 6
输出 #1
1 3 2 5 4 6
输入 #2
6 6
1 3
2 3
2 5
3 4
4 5
4 6
输出 #2
1 3 2 4 5 6
说明/提示
【数据规模与约定】
对于 100 % {100\%} 100%的数据和所有样例, 1 ≤ n ≤ 50001 且 m = n − 1 或 m = n {1 \le n \le 50001且 m = n − 1或 m = n} 1≤n≤50001且m=n−1或m=n。
对于不同的测试点, 我们约定数据的规模如下:
解题思路
情况一:
m
=
n
−
1
{m=n-1}
m=n−1,情况二:
m
=
n
{m=n}
m=n.
情况一解法:
(
60
p
t
s
)
{(60pts)}
(60pts)时间复杂度:
O
(
n
l
o
g
n
)
{O(n~logn)}
O(n logn)
(实际上是排序加了复杂度,不然都是O(n)的 )
这种情况所遍历的图显然是棵树,所以我们先用邻接表存图,然后根据贪心思想从一开始依次遍历(优先走小的连边),这里涉及邻接表连边的排序.(这里建议用库函数的(可以直接排),而手写的邻接表排序得提前,而且排的方向是好像是反的(具体看代码(玄♂学 ))).再在遍历的过程中加入一个数组储存遍历顺序即可.
#include<bits/stdc++.h>
#define N 500005
#define in read()
using namespace std;
int n,m,tot,k;
int fi[N],nxt[2*N],to[2*N],ans[N];
bool ju[N];
struct zb{
int x,y;}a[2*N];//开两倍否则可能会爆
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch^48);ch=getchar();}
return i*f;
}
inline void lian(int u,int v)
{
nxt[++tot]=fi[u];
fi[u]=tot;
to[tot]=v;
}
inline bool cmp(const zb &a,const zb &b)//*这里我想了半天,但时在不明白为何从大到小.
{
return a.y>b.y;
}
inline void Dfs1(int u)
{
ju[u]=1;
ans[++k]=u;
for(int i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(ju[v])continue;
ju[v]=1;
Dfs1(v);
}
return;
}
int main()
{
n=in,m=in;
for(int i=1;i<=m;i++)
{
a[i].x=in,a[i].y=in;
a[i+m].x=a[i].y,a[i+m].y=a[i].x;
}//双向都要存.
sort(a+1,a+2*m+1,cmp);//必须先排,不然连边之后很难操作(反正我不会)
for(int i=1;i<=2*m;i++)
lian(a[i].x,a[i].y);
if(n==m+1)
{
Dfs1(1);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}
else printf("orz I can not do it!\n")
return 0;
}
然后妥妥的60分就到手了.
情况二解法:
(
100
p
t
s
)
&
&
加
强
(
80
p
t
s
)
{(100pts)\&\&加强(80pts)}
(100pts)&&加强(80pts)时间复杂度:
O
(
n
2
)
{O(n^2)}
O(n2)
这种情况显然有且只有一个环(找环上的点用拓扑排序),而且我们发现每次遍历完所有点肯定有一条边是可以不用走的,而且这条边一定在环上.所以我们先把环上的所有点找出来,然后枚举每一条环上的边并删去,再以此Dfs遍历一遍,若所得的得的字典序更小则更新答案即可.
(拓扑在图论是个好东西qwq,强烈建议大家没学得去学学!)
#include<bits/stdc++.h>
#define N 500005
#define in read()
using namespace std;
int n,m,tot,k,dt1,dt2;
int fi[N],nxt[2*N],to[2*N],ans[N],res[N],rd[N];
bool ju[N],use[N];
struct zb{
int x,y;}a[2*N];
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch^48);ch=getchar();}
return i*f;
}//快读加速
inline void lian(int u,int v)
{
nxt[++tot]=fi[u];
fi[u]=tot;
to[tot]=v;
}
inline bool cmp(const zb &a,const zb &b)
{
return a.y>b.y;
}
inline void work()
{
if(!ans[1])//第一次要特判
{
for(int j=1;j<=n;j++)
ans[j]=res[j];
return;
}
for(int i=1;i<=n;i++)
{
if(ans[i]==res[i])continue;
if(res[i]>ans[i])return;
for(int j=1;j<=n;j++)
ans[j]=res[j];
return;
}
}
inline void Dfs1(int u)
{
ju[u]=1;
ans[++k]=u;
for(int i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(ju[v])continue;
ju[v]=1;
Dfs1(v);
}
return;
}
inline void Dfs2(int u)
{
res[++k]=u;
ju[u]=1;
for(int i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(ju[v]||(u==dt1&&v==dt2)||(u==dt2&&v==dt1))continue;
ju[v]=1;
Dfs2(v);
ju[v]=0;//注意这里是环所以要回溯!
}
ju[u]=0;
return;
}
inline void topu()//拓扑排序,处理双向图时入度为1就进队
{
queue<int>q;
for(int i=1;i<=n;i++)
{
if(rd[i]==1){
use[i]=1;
q.push(i);
}
}
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=fi[x];i;i=nxt[i])
{
int y=to[i];
rd[y]--;
if(rd[y]==1){
use[y]=1;
q.push(y);
}
}
}
return;
}
int main()
{
n=in,m=in;
for(int i=1;i<=m;i++)
{
a[i].x=in,a[i].y=in;
a[i+m].x=a[i].y,a[i+m].y=a[i].x;
rd[a[i].x]++,rd[a[i].y]++;
}
sort(a+1,a+2*m+1,cmp);
for(int i=1;i<=2*m;i++)
lian(a[i].x,a[i].y);
if(n==m+1)
{
Dfs1(1);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}
else
{
topu();
for(int i=1;i<=m;i++)
{
int x=a[i].x,y=a[i].y;
if(use[x]||use[y])continue;//不在环上就跳过.
dt1=x,dt2=y,k=0;//用变量代替邻接矩阵表示被删边可大大节约空间
Dfs2(1);
work();//更新答案
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}
return 0;
}
然后交上此代码就可以A了!
完结撒花!
更优算法待更新…