P5049 [NOIP2018 提高组] 旅行
P5049 [NOIP2018 提高組] 旅行
題意:
一棵樹(可能是基環樹),從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; }總結
以上是生活随笔為你收集整理的P5049 [NOIP2018 提高组] 旅行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联邦学习:按Dirichlet分布划分N
- 下一篇: P2607 [ZJOI2008]骑士