题意:
给定n个点m条边的无向图,
从点1出发,每到一个新点就记录编号。
要求找到字典序最小的走法,输出方案。
数据范围:n<=5e3,m=n-1或m=n
解法:
根据数据范围发现,要么是普通树,要么是基环树。
如果是普通树,那么对边按字典序从小到大排序,优先走字典序小的点就行了。
如果是基环树,那么可以先找到环上的边,
然后枚举环上的边删除,删除之后就是普通树了,按普通树的方法计算,取最优答案即可。
找环用拓扑排序。
ps:
这题卡vector,存图得用链式前向星,
注意链式前向行是倒着存边的,因此排序需要按字典序从大到小排序。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=5e3+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
int c1[maxm],c2[maxm],cc;//记录环上的边
int in[maxm];
int delx,dely;
//
int temp[maxm],num;
int ans[maxm];
int n,m;
struct E{
int a,b;
bool operator<(const E& a)const{
return b>a.b;
}
}e[maxm<<1];
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int x,int fa){
temp[++num]=x;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(v==fa)continue;
if(x==delx&&v==dely)continue;
if(x==dely&&v==delx)continue;
dfs(v,x);
}
}
void topo(){//拓扑排序,最后度数不为1的就是环上的点
queue<int>q;
int cnt=0;
for(int i=1;i<=n;i++){
if(in[i]==1){
q.push(i);
cnt++;
}
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(in[v]>1){
in[v]--;
if(in[v]==1){
q.push(v);
cnt++;
}
}
}
}
for(int x=1;x<=n;x++){//找环上的边
if(in[x]==1)continue;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(in[v]==1)continue;
cc++;
c1[cc]=x,c2[cc]=v;
}
}
}
void check(){//更新答案
if(ans[1]==0){
swap(temp,ans);
return ;
}
for(int i=1;i<=n;i++){
if(temp[i]<ans[i]){
swap(temp,ans);
return ;
}else if(temp[i]>ans[i]){
return ;
}
}
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&e[i].a,&e[i].b);
e[i+m]={e[i].b,e[i].a};
in[e[i].a]++,in[e[i].b]++;
}
//按字典序加边
sort(e+1,e+1+m*2);
for(int i=1;i<=m*2;i++){
add(e[i].a,e[i].b);
}
//
if(m==n-1){//普通树
num=0;
dfs(1,1);
for(int i=1;i<=n;i++){
printf("%d ",temp[i]);
}
}else{//基环树
topo();
for(int i=1;i<=cc;i++){//枚举删除的边
delx=c1[i];
dely=c2[i];
num=0;
dfs(1,1);
check();
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
}
return 0;
}