簡介
定義:
- 尤拉回路:透過圖中每條邊恰好一次的迴路
- 尤拉通路:透過圖中每條邊恰好一次的通路
- 尤拉圖:具有尤拉回路的圖
- 半尤拉圖:具有尤拉通路但不具有尤拉回路的圖
摘自: oi-wiki。
定義說白了就是小學的一筆畫問題,這裡直接給出三道例題。P7771 【模板】尤拉路徑,CF508D 和 CF36E。
例題
P7771 【模板】尤拉路徑
思路
模板題,沒有思路。直接講一下求尤拉路徑的方法即可。
首先,對於一個圖,它要存在尤拉路徑的必要條件很簡單。就是奇點的個數要為 0 或者 2。其他情況都是無解的。這兩種情況對應的圖的形態長這樣捏:
當然這也不是唯一解。其中第一個圖中沒有奇點,而第二個圖中奇點為 1 和 2。
這裡給出求解尤拉路徑的虛擬碼:
void dfs(int u){
遍歷u的所有出邊
如果該邊還存在
刪掉這條邊
dfs(該邊去往的點)
u入棧
}
這裡給出一點 luogu 的大部分題解都沒有解釋到的地方。為什麼不能遍歷到一個點就將該點入棧,非要迴圈結束後才可以?這裡舉出一個例子:如果有一個無向圖長得像一個 8 字,就像下面這樣:
當我們走到 5 的時候會發生一個事情。就是說這個時候我們既可以選擇往 1 走也可以往 4 走。我們希望的是向 4 走。如果我們最後再入棧那麼當它 dfs 到 1 的時候就只會推掉 5 和 1 之間的部分,再從 5 開始繼續搜尋。相當於我們最開始的 \([1,7,6,5]\) 加上 \([5,4,3,5]\) 以及 \([5,1]\) 就是我們最後的答案。這裡需要倒序輸出,這裡借鑑了 Marsrayd 大佬的 部落格。這裡是這樣說的:
感性理解倒序輸出的原因:如果是尤拉回路,那麼遍歷到哪,輸出到哪也是對的,因為不管怎麼走都會繞某個環走回起點,所以不到最後不會出棧,然而尤拉路徑會出現邊都被走過了,走不回起點,最後會停留在終點,遇到這種情況這種路徑會最先出棧,於是只要把這個路徑先走了,前面就和尤拉回路一樣隨便走就行,不會出棧,於是倒序輸出就是對的
膜拜大佬。這個說得非常清楚啊。
CF508D
思路
此題很板啊。我們直接把這個字串的前面的兩個字元和後面的兩個字元各看作一個點,連一條單向邊。因為這題要求相同的字串之間連線。那麼我們把它轉成 int 了之後就自然而然連上邊了。
我們需要再記錄一個每個點的出度和入度。來判斷改圖是否存在尤拉路徑。
其實就是一個板題,這邊給出可愛的程式碼:
AC code
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
namespace WYL{
const int N=3e5+10;
const int INF=2e5;
vector<int> edge[N];
vector<int> point;
int n,outde[N],inde[N],Start;
string ans;
void dfs(int u){
while(!edge[u].empty()){
int to=edge[u][edge[u].size()-1];
edge[u].pop_back();
dfs(to);
}
ans+=(char)(u%256);
}
int main(){
cin>>n;
memset(outde,0,sizeof(outde));
memset(inde,0,sizeof(inde));
string s;
for(int i=1;i<=n;i++){
cin>>s;
int u=s[0]*256+s[1],v=s[1]*256+s[2];
edge[u].push_back(v);
outde[u]++;
inde[v]++;
Start=u;
}
int k=3e5;
for(int i=0;i<=k;i++){
if(edge[i].size()!=0){
point.push_back(i);
}
}
if(n==1){
cout<<"YES"<<endl;
cout<<s<<endl;
return 0;
}
int flag=0,cnt=0;
for(int i=0;i<=INF;i++){
if(outde[i]!=inde[i]){
flag=1;
}
if(abs(outde[i]-inde[i])==1){
cnt++;
}
if(abs(outde[i]-inde[i])>1&&outde[i]!=inde[i]){
cout<<"NO"<<endl;
return 0;
}
if(outde[i]-inde[i]==1){
Start=i;
}
}
if(flag==1&&cnt!=2){
cout<<"NO"<<endl;
return 0;
}
dfs(Start);
ans+=Start/256;
if(ans.size()<n+2){
cout<<"NO"<<endl;
return 0;
}
cout<<"YES"<<endl;
reverse(ans.begin(),ans.end());
cout<<ans<<endl;
return 0;
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
WYL::main();
return 0;
}
CF36E
寫在前面的話
也許是本人太蒟了。本人認為本題的程式碼難度遠高於 CF508D,但是 CF 的評分只差了 100 分(喜。
題意
翻譯說得很清楚,這裡再重複一下。
給你一張無向圖,不保證連通以及可能有重邊。讓你求兩條路徑,使其正好能夠覆蓋所有的邊,按邊的編號輸出答案。
思路
首先。這道題與 CF508D 的不同點在於這道題要求的是兩條路徑。於是我們嘗試分類討論。
因為不保證連通。我們首先知道,如果連通塊的數量是 1 或者 2 的時候都有解。為什麼?這個很顯然,如果有大於兩個的連通塊。那麼一定不存在兩條路徑能夠覆蓋掉所有的點。於是連通塊數量大於 2 時我們可以直接把這個圖 ban 掉。
好的現在我們來看連通塊為 1 的情況。因為我們要求的路徑相當於就是一個尤拉路徑。所以在連通塊為 1 的情況下度數為奇數的個數就只可能是 0,2 或者 4。其中呢 0 和 2 都比較好處理。只要當路徑長度比 2 大就一定可以分成兩坨。但是呢 4 就需要想一想了。這裡提供一種蒟蒻的想法:我們找到兩個沒有連邊的奇點將它們之間連上一條虛邊(不是重鏈剖分裡的那個捏)。跑一遍尤拉路徑,然後再把那條虛邊給刪掉。得到的兩個部分就是我們所求的,這裡給出一個例子:
輸入資料:
5
1 2
1 4
1 3
1 5
3 6
這裡我連上了 5 與 6 之間的邊。再刪掉再刪掉紅色的邊,就得到了點集為 \([1,4,5]\) 與 \([1,2,3,6]\) 的答案。
連通塊為 1 的情況看完了。現在來研究連通塊數量為 2 的。顯然只有在兩個連通塊的奇點個數都為 0 或者都為 2 時才是有解的。如果有解直接各找一個尤拉路徑就可以啦。
AC code
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
namespace WYL{
const int N=1e5+10;
int n,to[N],nxt[N],head[N],tot,du[N],kid[N],kcnt,kminn[N];
bool mark[N],vis[N];
vector<int> jidian,path,jidian1,jidian2,path1,path2;
void add(int u,int v){
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
return;
}
void dfs(int id,int k){
kid[id]=k;
for(int i=head[id];i;i=nxt[i]){
if(kid[to[i]]==0&&du[to[i]]!=0){
dfs(to[i],k);
}
}
return;
}
int find_edge(int x,int y){
for(int i=head[x];i;i=nxt[i]){
if(to[i]==y&&!mark[(i+1)/2]){
return i;
}
}
return -1;
}
void Euler(int u,vector<int> &tmp){
if(du[u]==0){
tmp.push_back(u);
return;
}
for(int i=head[u];i;i=nxt[i]){
// cout<<u<<"->"<<to[i]<<endl;
if(vis[(i+1)/2]){
continue;
}
vis[(i+1)/2]=true;
du[to[i]]--;
du[u]--;
// cout<<"***"<<u<<"->"<<to[i]<<endl;
Euler(to[i],tmp);
}
tmp.push_back(u);
return;
}
void solve(int l,int r,vector<int> path){
for(int i=l;i<=r;i++){
int opt=(find_edge(path[i],path[i+1])+1)/2;
cout<<opt<<" ";
mark[opt]=true;
}
return;
}
int main(){
cin>>n;
if(n==1){
cout<<"-1"<<endl;
return 0;
}
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
du[x]++;
du[y]++;
add(x,y);
add(y,x);
}
for(int i=1;i<=10005;i++){
if(du[i]&&kid[i]==0){
kminn[kcnt+1]=i;
dfs(i,++kcnt);
}
if(du[i]%2==1){
jidian.push_back(i);
}
}
if(kcnt>2){
cout<<"-1"<<endl;
return 0;
}
if(kcnt==1){
if(jidian.size()==4){
for(int i=0;i<4;i++){
for(int j=i+1;j<4;j++){
if(find_edge(jidian[i],jidian[j])!=-1){
continue;
}
swap(jidian[0],jidian[i]);
swap(jidian[1],jidian[j]);
goto end;
}
}
end:
int x=jidian[0],y=jidian[1];
du[x]++;
du[y]++;
add(x,y);
add(y,x);
memset(vis,0,sizeof(vis));
Euler(jidian[2],path);
int place=0;
while((path[place]!=jidian[0]||path[place+1]!=jidian[1])&&(path[place]!=jidian[1]||path[place+1]!=jidian[0])){
place++;
}
cout<<place<<endl;
solve(0,place-1,path);
cout<<endl;
cout<<n-place<<endl;
solve(place+1,path.size()-2,path);
return 0;
}else{
path.clear();
if(jidian.size()!=0&&jidian.size()!=2){
cout<<"-1"<<endl;
return 0;
}
if(jidian.size()==0){
Euler(kminn[1],path);
}else if(jidian.size()==2){
Euler(jidian[0],path);
}
cout<<"1"<<endl;
solve(0,0,path);
cout<<endl;
cout<<path.size()-2<<endl;
solve(1,path.size()-2,path);
return 0;
}
}else if(kcnt==2){
for(int i=0;i<jidian.size();i++){
if(kid[jidian[i]]==1){
jidian1.push_back(jidian[i]);
}else{
jidian2.push_back(jidian[i]);
}
}
if(jidian1.size()!=0&&jidian1.size()!=2){
cout<<"-1"<<endl;
return 0;
}
if(jidian2.size()!=0&&jidian2.size()!=2){
cout<<"-1"<<endl;
return 0;
}
path1.clear();path2.clear();
if(jidian1.size()==0){
int place=1;
while(kid[place]!=1){
place++;
}
Euler(place,path1);
}else{
Euler(jidian1[0],path1);
}
cout<<path1.size()-1<<endl;
solve(0,path1.size()-2,path1);
cout<<endl;
if(jidian2.size()==0){
int place=1;
while(kid[place]!=2){
place++;
}
Euler(place,path2);
}else{
Euler(jidian2[0],path2);
}
cout<<path2.size()-1<<endl;
solve(0,path2.size()-2,path2);
return 0;
}
return 0;
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
WYL::main();
return 0;
}
/*
10
7 4
7 4
6 1
2 3
2 1
1 6
4 2
8 4
4 8
3 5
*/