3 条题解

  • 0
    @ 2026-9-2 12:53:03

    首先考虑前 60 分,即一棵树的做法。
    由于要求字典序最小,我们很容易想到的就是贪心的做法,即从 1 号节点开始,每次走编号最小的一条,易证这样的字典序是最小的,那么代码实现时我们如果还用链式前向星的话,写起来就会非常的麻烦。
    于是考虑 vector 存图,对于每个点把所有和它连结的点的编号从小到大排序。
    因为我们在使用 vector 时,代码是这么写的:

        for(int i=0;i<v[u].size();i++)
    

    所以可以保证每次往最小的一边走,然后我们从1号节点出发,跑一遍 Dfs 就可以得到答案了。
    代码如下(15/25 60分做法):

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,ans[5005],tot,a[5005],lat[5005],sum;
    vector <int> v[5005];
    bool Check()
    {
        for(int i=1;i<=n;i++) if(!a[i]) return 0;
        for(int i=1;i<=n;i++) if(a[i]!=ans[i]) return ans[i]>a[i];
    }
    void Dfs(int u,int faz)
    {
        if(sum>n) return;
        a[sum]=u;
        for(int i=0;i<v[u].size();i++)
        {
            if(v[u][i]==faz) continue;
            sum++;
            Dfs(v[u][i],u);
        }
    }
    int main()
    {
        memset(ans,60,sizeof(ans));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        for(int i=1;i<=n;i++) sort(v[i].begin(),v[i].end());
        sum=1;
        Dfs(1,0);
        memcpy(ans,a,sizeof(a));
        for(int i=1;i<=n;i++) printf("%d ",ans[i]);
        return 0;
    }
    

    那还有剩下的 40 分呢?其实也不难,我们可以枚举删除一条边,再按原来的办法跑 Dfs 就行。
    但为什么这样可行呢?因为如果一个图里有环的话(nmn\leq m 在没有重边自环的情况下是肯定有环的),这一个环里肯定有一条边是用不到的,正确性显而易见,因为我们下一步只能往没走过的地方走,或者是朝上一个地方走,在这种情况下我们能走的点是 nn,而能经过的边只有 n1n-1 条,对于一个 xx 个点 xx 个边的环来说,肯定是有一条边用不到的(基环树嘛),所以我们就枚举这条用不到的边,然后跑 Dfs 即可。
    但事实证明如果直接枚举所有边在洛谷数据会超时,T 了最后两个点,开 O2 是可以过,但还有一种我脑补的优化方法,就是先跑一遍 Dfs,找到环,然后只删环上的边,为什么呢?因为我们可以想到,如果你删的边不是在环上而在别的地方,如果1号节点还能走到环上,时间复杂度是会跑满的(这也是为什么我在程序里用了一个 cir 的布尔变量),这样就很容易超时了。
    这种写法就交给各位了,这里附上我的代码:
    (23/25 92 做法,O2 25/25 100)

    // luogu-judger-enable-o2
    #include<bits/stdc++.h>
    using namespace std;
    int n,m,ans[5005],tot,a[5005],lat[5005],sum,fr[5005],to[5005],del;
    vector <int> v[5005];
    bool cir;
    bool Check()
    {
        for(int i=1;i<=n;i++) if(!a[i]) return 0;
        for(int i=1;i<=n;i++) if(a[i]!=ans[i]) return ans[i]>a[i];
    }
    void Dfs(int u,int faz)
    {
        if(sum>n)
        {
            cir=1;
            return;
        }
        a[sum]=u;
        for(int i=0;i<v[u].size();i++)
        {
            if((u==fr[del] && v[u][i]==to[del]) || (v[u][i]==fr[del] && u==to[del]) || v[u][i]==faz) continue;
            sum++;
            Dfs(v[u][i],u);
        }
    }
    int main()
    {
        memset(ans,60,sizeof(ans));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
            fr[i]=x;
            to[i]=y;
        }
        for(int i=1;i<=n;i++) sort(v[i].begin(),v[i].end());
        if(m==n-1)
        {
            sum=1;
            Dfs(1,0);
            memcpy(ans,a,sizeof(a));
        }
        else if(n==m)
        {
            for(int i=1;i<=m;i++)
            {
                memset(a,0,sizeof(a));
                del=i;
                sum=1;
                cir=0;
                Dfs(1,0);
                if(!cir && Check()) memcpy(ans,a,sizeof(a));
            }
        }
        for(int i=1;i<=n;i++) printf("%d ",ans[i]);
        return 0;
    }
    
    • 0
      @ 2026-6-22 8:53:17

      D30 基环树 P5022 [NOIP2018 提高组] 旅行_哔哩哔哩_bilibili

      P5022 [NOIP 2018 提高组] 旅行 - 洛谷

      基环树的遍历最小字典序问题:出边排序。暴力断边。剪枝优化。

      // 基环树 遍历最小字典序 暴力 O(n^2)
      #include<bits/stdc++.h>
      using namespace std;
      
      const int N=5010;
      int n,m,a,b;
      vector<int> e[N];
      pair<int,int> edge[N];
      int du,dv,vis[N],cnt,better;
      vector<int> path(N,N);
      
      void dfs1(int x){ //树搜索
        vis[x]=1;
        path[cnt++]=x;
        for(int y:e[x])if(!vis[y]) dfs1(y);
      }
      bool dfs2(int x){
        if(!better){
          //剪枝:若序号变大则回退,变小则走完
          if(x>path[cnt]) return 1;
          if(x<path[cnt]) better=1;
        }
        vis[x]=1;
        path[cnt++]=x;
        for(int y:e[x]){
          if(vis[y])continue;
          if(y==du&&x==dv)continue;
          if(y==dv&&x==du)continue;
          if(dfs2(y)) return 1;
        }
        return 0;
      }
      int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
          scanf("%d%d",&a,&b);
          e[a].push_back(b);
          e[b].push_back(a);
          edge[i]={a,b};
        }
        for(int i=1;i<=n;i++)sort(e[i].begin(),e[i].end()); //出边排序
          
        if(m==n-1) dfs1(1);
        else{
          for(int i=1;i<=m;i++){ //暴力断边
            du=edge[i].first;
            dv=edge[i].second;
            memset(vis,0,sizeof vis);
            cnt=better=0;
            dfs2(1);
          }
        }
        for(int i=0;i<n;i++)printf("%d ",path[i]);
      }
      
      • 0
        @ 2025-10-8 16:53:34
        #include<bits/stdc++.h>
        using namespace std;
        const int N=5010;
        vector<int>G[N];
        pair<int, int> edge[N];
        
        int better, cnt, dx, dy, v[N], path[N];
        bool dfs(int x) 
        {
        	if(!better && x>path[cnt+1])return 0;
        	if(!better && x<path[cnt+1])better=1;
        	v[x]=1;
        	path[++cnt]=x;
        	for(int y:G[x]) if(!v[y])
        	{
        		if((x==dx&&y==dy)||(y==dx&&x==dy))continue;
        		if(!dfs(y))return 0;
        	}
        	return 1;
        }
        int main() 
        {
        	int n, m;scanf("%d%d", &n, &m);
        	for(int i=1, x, y; i<=m; i++) 
        	{
        		scanf("%d%d", &x, &y);
        		G[x].push_back(y);
        		G[y].push_back(x);
        		edge[i]= {x, y};
        	}
        	for(int i=1; i<=n; i++)sort(G[i].begin(), G[i].end());
        	
        	memset(path, 0x3f, sizeof(path));
        	if(n==m+1) dfs(1);
        	else 
        	{
        		for(int i=1; i<=m; i++) //枚举断边
        		{ 
        			dx=edge[i].first;
        			dy=edge[i].second;
        			memset(v, 0, sizeof(v));
        			cnt=better=0;
        			bool bk=dfs(1);
        		}
        	}
        	for(int i=1; i<=n; i++)printf("%d ", path[i]);
        	return 0;
        }
        
        • 1

        D30 基环树 遍历最小字典序[NOIP 2018 提高组] 旅行

        信息

        ID
        809
        时间
        1000ms
        内存
        512MiB
        难度
        8
        标签
        递交数
        91
        已通过
        17
        上传者