[NOIP2018 提高组] 旅行

题目在这里或者这里
分析题目,m只有n-1和n两种情况。
1、先看n-1的情况,在这种情况下,其实就是一棵树,要想求最小字典序的序列,我们只需要从1开始搜索(因为是无向边,无论哪一个节点都可以当作根节点),在该结点有多个分节点的情况下优先选择较小的节点即可,在搜索的过程中顺便把路径记录下来。在看当前节点的分节点的时候,这里可以考虑用一个vector数组去存,这样可以直接找到这个节点的所有相邻节点,还可以进行sort排序,遍历节点的时候可以从小到大。
2、再看m等于n的情况,在这种情况下,可以看作将m条边中的某一条割断之后继续像1一样进行搜索,然后取字典序最小的一个路径即可。为什么可以看作将某一条边割断呢?因为要联通n个点的话,其实只需要n-1条边就可以了,想不通的话,可以画一个带环的树来看看。至于为什么需要割断一条边,可以看图
如果我们不切断2、4这条边,那么得到的序列应该是1、2、4、3,但是如果我们切断了这条边,那么我们得到的序列就是1、2、3、4,比刚才的情况要小。可以用一个边结构体来存m条边,下边的时候可以直接遍历每条边,假设这条边被割断了,然后继续搜索即可,搜索完之后判断是否要更新ans数组
3、最后一点,记得开O2或者O3
代码如下:

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define ll long long
#define pii pair<ll,ll>
using namespace std;
//typedef long long int ll;
//const ll inf = 1e16;
inline ll read()
{
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
vector<int> a[6000];
ll ans[6000];
ll temp[6000];
ll vis[6000];
struct node
{//储存边
    ll from,to;
} edge[6000];
ll tot,du,dv,n,m;
void dfs1(ll u)
{//第一种情况的搜索
    if(vis[u])
    {
        return ;
    }
    ans[tot++]=u;
    vis[u]=1;
    for(ll i=0; i<a[u].size(); i++)
    {//遍历节点u的邻节点
        ll v=a[u][i];
        dfs1(v);
    }
}
void dfs2(ll u)
{//第二种情况的搜索
    if(vis[u])
    {//如果这个点遍历过,直接退出
        return ;
    }
    temp[tot++]=u;
    vis[u]=1;
    for(ll i=0; i<a[u].size(); i++)
    {
        ll v=a[u][i];
        if((u==du&&v==dv)||(v==du&&u==dv))
        {//如果这条边是被割断的,直接跳过
            continue;
        }
        dfs2(v);
    }
}
ll check()
{//检查是否更新答案
    for(ll i=0; i<n; i++)
    {
        if(temp[i]<ans[i])
        {//如果更小,更新
            return 1;
        }
        if(temp[i]>ans[i])
        {//如果比ans更大,不更新
            return 0;
        }
    }
    return 0;
}
void fuzhi()
{
    for(ll i=0; i<n; i++)
    {
        ans[i]=temp[i];
    }
}
int main()
{
    ll u,v;
    scanf("%lld%lld",&n,&m);
    for(ll i=1; i<=m; i++)
    {
        scanf("%lld%lld",&u,&v);
        a[u].push_back(v);
        a[v].push_back(u);
        edge[i].from=u;
        edge[i].to=v;
    }
    for(ll i=1; i<=n; i++)
    {//对每个节点的邻接点排序
        sort(a[i].begin(),a[i].end());
    }
    if(m==n-1)
    {
        tot=0;
        dfs1(1);
    }
    else
    {
        memset(ans,0x3f,sizeof(ans));
        for(ll i=1; i<=m; i++)
        {
            memset(vis,0,sizeof(vis));
            tot=0;
            du=edge[i].from;
            dv=edge[i].to;
            dfs2(1);
            if(tot<n-1)
            {//如果图被割断,变成两个部分,直接找下一种情况
                continue;
            }
            if(check())
            {
                fuzhi();
            }
        }
    }
    for(ll i=0; i<n-1; i++)
    {
        printf("%lld ",ans[i]);
    }
    printf("%lld\n",ans[n-1]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值