欧拉路:欧拉路是指从图中任意一个点开始到图中任意一个点结束的路径,并且图中每条边通过的且只通过一次。
欧拉回路:欧拉回路是指起点和终点相同的欧拉路
无向图:
无向图存在欧拉路的充要条件:
- 图是连通的
- 所以点的度数位偶数,或者有且仅有2个点的度为奇数
如果所以点的度数都为偶数,那么就是欧拉回路,任意点都可以为起点
如果存在度数为奇数的点,那么呢两个奇数入度点为起点和终点
有向图:
- 每个点的入度等于出度,则存在欧拉回路(任意一点有度的点都可以作为起点)
- 除两点外,所有入度等于出度。这两点中一点的出度比入度大,另一点的出度比入度小,则存在欧拉路。取出度大者为起点,入度大者为终点。
如果不用输出路径,一般用并查集判断是否连通
如果要输出路径,一般用深搜找路径和判断连通,cnt==n,即连通
例题:
字典树+欧拉图(poj-2513):https://vjudge.net/problem/POJ-2513
题意:
有若干个棍子,棍头用字母表示,相同的棍头可以连接在一起,问能否把所有棍子连在一起?
解析:
把颜色转化为点,然后判断是否连通,是否入度全为偶或者只有2个奇
ac:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define MAXN 1000005
using namespace std;
char a[13],b[13];
int trie[MAXN][28];
int vis[MAXN];
int in[MAXN];
int cnt=0;
int f[MAXN];
int init()
{
for(int i=0;i<MAXN;i++)
f[i]=i;
}
int get(int x)
{
return x==f[x]?x:f[x]=get(f[x]);
}
int unite(int x,int y)
{
f[get(x)]=get(y);
}
void ins(char s[])
{
int rt=0;//根结点
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(trie[rt][c]==0)
trie[rt][c]=++cnt;
rt=trie[rt][c];
}
vis[rt]=cnt;
}
int getid(char s[])
{
int rt=0;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(trie[rt][c]==0)
return 0;
rt=trie[rt][c];
}
return vis[rt];
}
int main()
{
init();
int st;
while(scanf("%s%s",&a,&b)!=EOF)
{
int aa=getid(a);
if(aa==0)
{
ins(a);
aa=getid(a);
}
int bb=getid(b);
if(bb==0)
{
ins(b);
bb=getid(b);
}
in[aa]++;
in[bb]++;
st=aa;
unite(aa,bb);
}
int num=0,sign=0;
st=get(st);
for(int i=1;i<=cnt;i++)
{
if(in[i]==0)
continue;
if(in[i]%2==1)
num++;
if(get(i)!=st)
{
sign=1;
break;
}
}
if(sign==0&&(num==0||num==2))
printf("Possible\n");
else
printf("Impossible\n");
return 0;
}
有向图字典序输出欧拉路径:https://vjudge.net/problem/POJ-2337
题意:
单词接龙,如果能接龙,输出字典序最小的单词接龙
解析:
有向图的欧拉路径判断
1.所以点入度=出度,或者存在2个点,1个点入度=出度+1,一个点出度=入度+1(起点)
2.用dfs搜路径,用回溯法存路径,倒序输出
怎么建图保证是字典序最小呢?
我们用链式前向星建边,因为我们dfs回溯输出存路径,然后倒序输出路径,所以我们要从大到小建边
ctr[i]倒序排序,然后建边,判断是否有欧拉路,然后搜索欧拉路,存路径,倒序输出
ac:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define MAXN 2005
using namespace std;
string ctr[MAXN];
int to[MAXN<<2],nxt[MAXN<<2],val[MAXN<<2],head[MAXN<<2];
int in[MAXN<<2],out[MAXN<<2];
int tot=0;
void add(int u,int v,int w)
{
to[++tot]=v;
nxt[tot]=head[u];
val[tot]=w;
head[u]=tot;
}
int vis[MAXN<<2];
int ans[MAXN<<2];
int num=0;
void dfs(int x)
{
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(vis[i]==0)
{
vis[i]=1;
dfs(v);
ans[++num]=val[i];
}
}
}
void init()
{
memset(head,0,sizeof(head));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
memset(vis,0,sizeof(vis));
num=tot=0;
}
int main()
{
int t,n;
cin>>t;
while(t--)
{
init();
cin>>n;
for(int i=1;i<=n;i++)
cin>>ctr[i];
sort(ctr+1,ctr+n+1);//排序
for(int i=n;i>=1;i--)
{
int aa=ctr[i][0]-'a'+1;
int bb=ctr[i][ctr[i].length()-1]-'a'+1;
in[bb]++;
out[aa]++;
add(aa,bb,i);
}
int start=0,ed=0,sign=0;
for(int i=1;i<=26;i++)//起点和终点至多1个
{
if(out[i]-in[i]==1)//起点
{
if(start==0)
start=i;
else{
sign=1;
break;
}
}
else if(in[i]-out[i]==1)
{
if(ed==0)
ed=i;
else{
sign=1;
break;
}
}
else if(in[i]!=out[i])
{
sign=1;
break;
}
}
if(sign==0)
{
if(start==0)//为欧拉回路
{
for(int i=1;i<=26;i++)
if(in[i]!=0||out[i]!=0)
{
start=i;
break;
}
}
dfs(start);
if(num!=n)
cout<<"***"<<endl;
else{
for(int i=num;i>=1;i--)
{
cout<<ctr[ans[i]];
if(i!=1)
cout<<".";
else
cout<<endl;
}
}
}
else{
cout<<"***"<<endl;
}
}
return 0;
}
https://vjudge.net/problem/HDU-3018
题意:给出一个图,问几笔画才能经过所有边(这题对于单独点,不作计算)
解析:对于每一个连通块,分别求其欧拉路数目
如果该连体块是欧拉路,1笔画
如果不上欧拉路,欧拉路为奇数度/2
ac:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int f[MAXN];
int vis[MAXN];
int in[MAXN];
vector<int> vc[MAXN];
void init()
{
for(int i=0;i<MAXN;i++)
f[i]=i,vc[i].clear();
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));
}
int get(int x)
{
if(x!=f[x])
f[x]=get(f[x]);
return f[x];
}
void unite(int x,int y)
{
f[get(x)]=get(y);
}
int main()
{
int n,m,a,b;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
unite(a,b);
in[a]++;
in[b]++;
}
for(int i=1;i<=n;i++)
{
if(in[i]==0)
continue;
int x=get(i);
vis[x]=1;
vc[x].push_back(i);
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(vis[i]==1)
{
int sign=0;
for(int j=0;j<vc[i].size();j++)
{
int x=vc[i][j];
if(in[x]%2==1)
sign++;
}
if(sign==0||sign==2)
ans++;
else{
ans+=sign/2;
}
}
}
printf("%d\n",ans);
}
return 0;
}
https://ac.nowcoder.com/acm/contest/369/C
题意:
求一棵树的最短欧拉路之和
解析:
结论:最短欧拉路=2*树的所有边长和-树的直径
求树的直径:,先从任意一点出发,求其最长路径a,然后从最长路径端点出发,再求最长路径b,b就是该树的最长路径.
ac:
#include<bits/stdc++.h>
#define MAXN 200005
#define ll long long
using namespace std;
int to[MAXN<<1],val[MAXN<<1],head[MAXN<<1],nxt[MAXN<<1];
int tot=0;
ll maxs=0,st=-1;
void init()
{
maxs=0,st=-1;
tot=0;
memset(head,0,sizeof(head));
}
void add(int u,int v,int w)
{
to[++tot]=v;
val[tot]=w;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs(int u,int fa,ll sum)
{
if(sum>maxs)
maxs=sum,st=u;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa)
continue;
dfs(v,u,sum+val[i]);
}
}
int main()
{
init();
int n,a,b,w;
ll sum=0;
scanf("%d",&n);
for(ll i=0;i<n-1;i++)
{
scanf("%d%d%d",&a,&b,&w);
sum+=w;
add(a,b,w);
add(b,a,w);
}
dfs(1,0,0);
dfs(st,0,0);
printf("%lld\n",sum*2-maxs);
return 0;
}