[Noip2018]旅行
生活随笔
收集整理的這篇文章主要介紹了
[Noip2018]旅行
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
題目描述
小 Y 是一個愛好旅行的 OIer。她來到 X 國,打算將各個城市都玩一遍。
小Y了解到, X國的 n個城市之間有 m條雙向道路。每條雙向道路連接兩個城市。 不存在兩條連接同一對城市的道路,也不存在一條連接一個城市和它本身的道路。并且, 從任意一個城市出發,通過這些道路都可以到達任意一個其他城市。小 Y 只能通過這些 道路從一個城市前往另一個城市。
小 Y 的旅行方案是這樣的:任意選定一個城市作為起點,然后從起點開始,每次可 以選擇一條與當前城市相連的道路,走向一個沒有去過的城市,或者沿著第一次訪問該 城市時經過的道路后退到上一個城市。當小 Y 回到起點時,她可以選擇結束這次旅行或 繼續旅行。需要注意的是,小 Y 要求在旅行方案中,每個城市都被訪問到。
為了讓自己的旅行更有意義,小 Y 決定在每到達一個新的城市(包括起點)時,將 它的編號記錄下來。她知道這樣會形成一個長度為 n的序列。她希望這個序列的字典序 最小,你能幫幫她嗎? 對于兩個長度均為 n的序列 A和 B,當且僅當存在一個正整數 x,滿足以下條件時, 我們說序列 A的字典序小于 B。
對于任意正整數 1 ≤ i < x,序列 A的第 i個元素 A_i和序列 B的第 i個元素 B_i相同。
序列 A的第 x個元素的值小于序列 B的第 x個元素的值。
輸入格式
輸入文件共 m + 1行。第一行包含兩個整數 n,m(m ≤ n),中間用一個空格分隔。
接下來 m 行,每行包含兩個整數 u,v (1 ≤ u,v ≤ n),表示編號為 u和 v的城市之 間有一條道路,兩個整數之間用一個空格分隔。
輸出格式
輸出文件包含一行,n個整數,表示字典序最小的序列。相鄰兩個整數之間用一個 空格分隔。
首先貪心思路很容易想。
假設當前可以走到u,v兩個點,其中u的字典序小于v的字典序,我們顯然應該直接走u。因為如果走了v,那么之后再怎么走也走不出比走u字典序更小的路徑了。可以從兩個點拓展到更多的點。
所以總結一下,我們的貪心策略就是——走當前點連出去的字典序最小的點。
那么你前15組數據穩穩地過了。但后面的就有點不一樣——基環樹!那這個基環樹就把問題搞得復雜了。
根據題意,我們在走到環上時,我們可以往左走或者往右走。根據我們的貪心策略,我們往字典序小的一邊走。這里假設我們往左走。但是題目中說了,我們是可以往回走的,這個條件在基環樹上回發生什么化學反應呢?顯然,我們往左走了若干個點后,我們發現這個字典序還是挺大的,這時我們可以后悔一次!然后退回環的起始點,往右走,從而得到更優的解。那么例子也是很好舉的,樣例2就有一個:
Sample in6 6 1 3 2 3 2 5 3 4 4 5 4 6 Sample out1 3 2 4 5 6這種后悔的操作可以怎么理解呢?首先你面前有一個環上的點,你本來要走過去,但是你沒走。這可以理解為把你和你面前的點之間的邊斷掉了。所以我們的核心思路就是——斷邊。我們可以先找出環,然后枚舉環上的每條邊,把它斷掉,然后根據前60分的貪心思路跑一遍。重復跑了若干次后,我們去最優解即可。時間復雜度O(N^2),再一看數據,加點小小的常數優化(代碼中標出)是可以過的。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<map> #define maxn 5001 using namespace std;vector<int> to[maxn]; int dfn[maxn],fa[maxn],tot,s; bool inloop[maxn]; pair<int,int> del1,del2; int n,m; int ans[maxn],path[maxn],d; map<pair<int,int>,bool> use;inline int read(){register int x(0),f(1); register char c(getchar());while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();return x*f; }//基環樹的情況 void dfs_getloop(int u){dfn[u]=++tot;for(register int i=0;i<to[u].size();i++){int v=to[u][i];if(!dfn[v]) fa[v]=u,dfs_getloop(v);else if(dfn[u]<dfn[v]){inloop[v]=true;do{ inloop[fa[v]]=true,v=fa[v]; }while(v!=u);}} } void dfs(int u,int pre){path[++d]=u;if(inloop[u]&&!s) s=u;for(register int i=0;i<to[u].size();i++){int v=to[u][i];if(v==pre||v==s) continue;if(make_pair(u,v)==del1||make_pair(u,v)==del2) continue;dfs(v,u);} } inline void cmp(){//比較答案for(register int i=1;i<=n;i++){if(path[i]<ans[i]) break;if(path[i]>ans[i]) return;}for(register int i=1;i<=n;i++) ans[i]=path[i]; }//樹的情況 void dfs2(int u,int pre){ans[++d]=u;for(register int i=0;i<to[u].size();i++){int v=to[u][i];if(v==pre) continue;dfs2(v,u);} }int main(){n=read(),m=read();for(register int i=1;i<=m;i++){int u=read(),v=read();to[u].push_back(v),to[v].push_back(u);}//按字典序排序,實際上這里用基排可以做到O(N)for(register int i=1;i<=n;i++) sort(to[i].begin(),to[i].end());if(m==n){dfs_getloop(1);memset(ans,0x3f,sizeof ans);for(register int i=1;i<=n;i++) if(inloop[i]){for(register int j=0;j<to[i].size();j++){int v=to[i][j];if(inloop[v]&&!use[make_pair(i,v)]){d=s=0,del1=make_pair(i,v),del2=make_pair(v,i);//斷掉雙向邊use[make_pair(i,v)]=true,use[make_pair(v,i)]=true;//這里用map記一下可以把我們的遍歷次數減半dfs(1,0),cmp();}}}}else dfs2(1,0);for(register int i=1;i<=n;i++) printf("%d ",ans[i]);puts("");return 0; }顯然我們的做法太暴力了,然后有人做了個這道題的加強版數據,n≤500000。經過分析和打表發現,我們所有刪邊的操作中好像也就只有那一次可以得到正解,所以我們要想辦法得到這條邊。
我們回想一下我們刪邊的原因:退回去換一個方向走的答案比繼續往下走更優。這里我們注意到,我們退回去時并不是直接退回到環的起始點,而是在往回走的每個點上把沒走過的地方走完了才繼續往回走。而走沒走過的地方顯然也需要遵循我們的貪心策略。那么什么時候往回走會更優呢?假設我們最近的一個還有地方沒走的祖先是u,它的沒走過的字典序最小的兒子為son(u),當前點為v,當前點的下一個在環上的點為son(v),那么顯然只有當son(u)<son(v)時往回走會更優——參照貪心的思路。所以我們只需要記下這個u即可。
不過我們還有需要注意的地方:
我們走的時候只能后悔一次。
只有當當前點的其它兒子都走過了時才可以選擇后悔。
時間復雜度為O(NlogN)。但由于本人寫法的原因(給兒子排序的部分),本人代碼的實際復雜度其實是
\[ O(N*\sum_{i=1}^{N}log_{2}Degree[i]) \]
可以發現增長率是很大的,但它確實是nlogn級別。不過在luogu開了O2可以過。
*degree表示這個點的度數
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<queue> #define maxn 500001 using namespace std;vector<int> to[maxn]; int dfn[maxn],fa[maxn],tot; bool inloop[maxn],vis[maxn],flag; int n,m; int ans[maxn],d;inline int read(){register int x(0),f(1); register char c(getchar());while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();return x*f; }void dfs_getloop(int u){dfn[u]=++tot;for(register int i=0;i<to[u].size();i++){int v=to[u][i];if(!dfn[v]) fa[v]=u,dfs_getloop(v);else if(dfn[u]<dfn[v]){inloop[v]=true;do{ inloop[fa[v]]=true,v=fa[v]; }while(v!=u);}} }void dfs(int u,int fa,int pre){if(vis[u]) return;ans[++d]=u,vis[u]=true;priority_queue< int,vector<int>,greater<int> > q;for(register int i=0;i<to[u].size();i++) if(!vis[to[u][i]]||to[u][i]!=fa) q.push(to[u][i]);//換成這種寫法的原因是為了更好地判斷環上的下一個點不是自己的父親while(q.size()){int v=q.top(); q.pop();if(inloop[v]&&v>pre&&!q.size()&&!flag){ flag=true; return; }if(inloop[v]&&q.size()) dfs(v,u,q.top());else dfs(v,u,pre);} }int main(){n=read(),m=read();for(register int i=1;i<=m;i++){int u=read(),v=read();to[u].push_back(v),to[v].push_back(u);}if(m==n) dfs_getloop(1);dfs(1,0,0x3f3f3f3f);for(register int i=1;i<=n;i++){if(i!=1) putchar(' ');printf("%d",ans[i]);}return 0; }轉載于:https://www.cnblogs.com/akura/p/10939812.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的[Noip2018]旅行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uniitest怎么传参数
- 下一篇: JavaScript 中数组方法 red