题意:
一棵树(可能是基环树),从1出发,每到达一个新的点就记录下编号。求一种走法使得记录下来的编号字典序最小。
1≤n≤500000
m=n−1 或 m=n
题解:
如果不是基环树,那直接每次走字典序小的点即可
对于基环树:
第一个方法:
暴力删边将基环树变为一颗普通的树,然后计算答案,复杂度是O(n * n)
这个方法好像只能过P5022 [NOIP2018 提高组] 旅行 这个没加强数据的题,P5049过不了
第二个方法:
参考题解
对于基环树,我们在环上跑到一半,另一半通过回溯到你刚到这个环的起点,接着DFS就可以了
例如下图,我们从1开始,走3走2然后回溯3,走4,走5回溯4,最后走6
如果在环上回溯之后(图中是2回溯到3),剩下的就不需要特殊处理
我们把在环上的点分成三种情况:
一、其出边为环上的那个点编号是其所有未被访问的出边中最小的,如下图。
相比于6和7,4更优,所以第一种情况不需要回溯,继续环上走就行
二:其出边为环上的那个点编号是其所有未被访问的出边中不是最大也不是最小的,如下图
先走4,然后走6,不需要回溯,继续环上走
三:其出边为环上的那个点编号是其所有未被访问的出边中最大的,如下图。
先走4,再走6,再回溯2
总结一下:
当我们在环上走时,只要当其出边中,在环上的那个点的编号最大时,且比回溯后第一个走的点还大,这时才回溯,其他都不用回溯正常跑DFS即可
我们用flag标记是否需要回溯,用tmp记录当前节点中第一个比环上的出边的节点还要大的节点,以便后面判断是否回溯
代码:
方法一:
#include<cstdio> #include<queue> #include<cstring> #include<algorithm> #define N 5010 using namespace std; struct node{ int to,next; }a[2*N]; struct line{ int x,y; }l[2*N]; int tot,n,m,t,ls[N],in[N],state[N],w[N],ans[N],x,y,q[N]; bool k[N][N],v[N]; void addl(int x,int y)//加边 { a[++tot].to=y; a[tot].next=ls[x]; ls[x]=tot; in[y]++; } bool topsort(){ int l=0,r=0; for (int i=1;i<=n;i++) if(in[i]==1) q[++r]=i; while(l<r) { int now=q[++l]; for (int i=ls[now];i;i=a[i].next){ int y=a[i].to; if(in[y]>1){ in[y]--; if(in[y]==1) q[++r]=y; } } } if(r==n) return true; return false; }//拓扑求环 bool cmp(line x,line y) {return x.y>y.y;} void dfs(int x)//走一遍 { state[++t]=x; v[x]=true; for(int i=ls[x];i;i=a[i].next) { int y=a[i].to; if(k[x][y]||v[y]) continue; dfs(y); } } void check()//判断是否为更小字典序 { int i; bool flag=false; for(i=1;i<=n;i++) if(state[i]<ans[i]) {flag=true;break;} else if(state[i]>ans[i]) return; if(!flag) return; for(;i<=n;i++) ans[i]=state[i]; } void get_ans(int xs)//暴力删边 { int x=xs,b=0,i,last=0; do { w[++b]=x; in[x]=1; for(i=ls[x];i;i=a[i].next) { int y=a[i].to; if(in[y]>1) {x=y;break;} } }while(i);//记录环的每个点 w[++b]=xs; for(int i=1;i<b;i++)//枚举删除的边 { k[w[i]][w[i+1]]=k[w[i+1]][w[i]]=true; memset(v,0,sizeof(v)); t=0; dfs(1); check(); k[w[i]][w[i+1]]=k[w[i+1]][w[i]]=false; } } int main() { memset(ans,127/3,sizeof(ans)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); l[i]=(line){x,y}; l[i+m]=(line){y,x}; } sort(l+1,l+1+2*m,cmp);//排序 tot=1; for(int i=1;i<=2*m;i++)//加边 {addl(l[i].x,l[i].y);} if(m==n-1)//普通的树 { dfs(1); for(int i=1;i<=n;i++) printf("%d ",state[i]); return 0; } topsort(); for(int i=1;i<=n;i++) if(in[i]>1) { get_ans(i); break; } for(int i=1;i<=n;i++) printf("%d ",ans[i]); }
方法二:
#include <iostream> #include <cstdio> #include <algorithm> #include <stack> #include <queue> #include <cstring> #include <vector> using namespace std; const int N = 500010; int n, m, vis[N], ans[N], cnt, f[N], rings[N], flag, tmp, temp, head[N], ver[N << 1], nex[N << 1], tot; struct Node { int x, y; }node[N << 1]; void add (int x, int y) { ver[++ tot] = y; nex[tot] = head[x]; head[x] = tot; } bool cmp (Node a, Node b) { return a.y > b.y; } inline int read () { int res = 0; char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') { res = (res << 3) + (res << 1) + (ch - 48); ch = getchar(); } return res; } void dfs (int x) { vis[x] = 1; ans[++ cnt] = x; for (int i = head[x]; i; i = nex[i]) { int y = ver[i]; if (!vis[y]) dfs(y); } } void dfsRing (int x, int fa) { if (flag) return; if (f[x] == 0) { f[x] = fa; }else if (f[x] != fa) { while (fa != x) { rings[fa] = 1; fa = f[fa]; } rings[x] = 1; flag = 1; return; } for (int i = head[x]; i; i = nex[i]) { int y = ver[i]; if (y == fa) continue; dfsRing(y, x); } } void sDfs (int x) { vis[x] = 1; ans[++ cnt] = x; if (rings[x]) { //判断x是否在环上 int flag = 0; for (int i = head[x]; i; i = nex[i]) { if (temp) break; //temp标记环上的回溯是否执行过了,因为一旦执行过环上的回溯,那么后面就不需要在环上回溯,只需正常跑DFS即可 int y = ver[i]; if (vis[y]) continue; if (rings[y]) { i = nex[i]; while (vis[ver[i]]) //已经被访问过的节点跳过 i = nex[i]; if (i) //i不为0即环上的出边不是最大的出边 tmp = ver[i]; //tmp记录第一个比环的出边大的那个点 else if (y > tmp) { //环上的出边是最大的出边且比我们回溯后第一次要走的节点还大 flag = 1; temp = 1; } break; } } for (int i = head[x]; i; i = nex[i]) { int y = ver[i]; if (vis[y]) continue; if (rings[y] && flag) continue; //flag = 1,因此回溯,不再走环上的出边 sDfs(y); } } else { for (int i = head[x]; i; i = nex[i]) { int y = ver[i]; if (vis[y]) continue; sDfs(y); } } } int main () { n = read(); m = read(); for (int i = 1; i <= m; i ++) { int u = read(), v = read(); node[i].x = u; node[i].y = v; node[i + m].x = v; node[i + m].y = u; } sort(node + 1, node + 2 * m + 1, cmp); for (int i = 1; i <= 2 * m; i ++) add(node[i].x, node[i].y); if (m == n - 1) { dfs(1); for (int i = 1; i <= n; i ++) printf("%d ", ans[i]); }else { dfsRing(1, 1); //一开始先找出所有在环上的点 flag = 0; tmp = 0x3f3f3f3f; sDfs(1); for (int i = 1; i <= n; i ++) printf("%d ", ans[i]); } return 0; }