文章目录
欧拉路
欧拉路径:一条路径经过图中每条边一次且仅一次,点不限次。
欧拉回路:一条回路经过图中每条边一次且仅一次,点不限次。
存在欧拉路径的条件:
无向图:每个点度数都为偶,或有且仅有两个顶点的度数为奇(一个为起点一个为重点)。
有向图:每个点出度等于入度,或存在一个点出比入大一,一个点入比出大一。
存在欧拉回路的条件:
无向图:每个点度数都为偶
有向图:每个点出度等于入度
套圈法求欧拉回路
大致思路就是每次向前走到不能走,dfs出一条极长路径,然后开始倒退,倒退出点的顺序就是欧拉回路上点的顺序,倒退到一个位置可以继续走了,就继续dfs向前。重复直到倒退完所有点。
因为度数为偶/入=出,每次dfs出的一定是一个圈,且剩下的边仍满足点度数为偶/入=出,继续在上面找圈。
传送门
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int M=2007,N=50;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,s,a[M][M],in[M],ans[M],sta[M],top;
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
void dfs(int x) {
sta[++top]=x;
For(i,1,N) if(a[x][i]) {
a[x][i]--;
a[i][x]--;
dfs(i);
break;
}
}
void fleury() {
sta[++top]=s; ans[0]=0;
while(top) {
int x=sta[top--];
int ok=0;
For(i,1,N) if(a[x][i]) {
ok=1; break;
}
if(ok) dfs(x);
else ans[++ans[0]]=x;
}
For(i,1,ans[0]-1) printf("%d %d\n",ans[i],ans[i+1]);
}
//#define DEBUG
int main() {
#ifdef DEBUG
freopen("1.in","r",stdin);
//freopen(".out","w",stdout);
#endif
read(T);
For(cs,1,T) {
if(cs!=1) printf("\n");
printf("Case #%d\n",cs);
read(n);
For(i,1,N) {
in[i]=0;
For(j,1,N) a[i][j]=0;
}
For(i,1,n) {
int x,y;
read(x); read(y);
a[x][y]++; a[y][x]++;
in[x]++; in[y]++;
s=x;
}
int fl=1;
For(i,1,N) if(in[i]&1) fl=0;
if(!fl) puts("some beads may be lost");
else fleury();
}
return 0;
}
CF723E One-Way Reform
どこでもドア
给定无向简单图,给每条边定向使尽可能多的点入度=出度。
所有度数为偶数的点都能满足。度数总和为偶,故度数为奇的点有偶数个。新建点向每个度数为奇的点连边跑跑欧拉回路即可找到方案。
//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=207;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m;
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int a[N][N],b[N][N],in[N];
void add(int u,int v) {
a[u][v]=a[v][u]=1; in[v]++; in[u]++;
}
int sta[N*N],top,vis[N],euler[N*N],cnt;
void dfs(int x) {
sta[++top]=x;
For(i,1,n+1) if(a[x][i]) {
a[x][i]--;
a[i][x]--;
dfs(i);
break;
}
}
void fleury() {
For(i,1,n) if(!vis[i]&&in[i]) {
cnt=0;
sta[++top]=i;
while(top) {
int x=sta[top--],ok=0;
For(j,1,n+1) if(a[x][j]) {
ok=1; break;
}
if(ok) dfs(x);
else {
vis[x]=1;
euler[++cnt]=x;
}
}
For(i,1,cnt-1) b[euler[i]][euler[i+1]]=1;
}
}
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
read(T);
while(T--) {
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(vis,0,sizeof(vis));
read(n); read(m);
For(i,1,m) {
int x,y;
read(x); read(y);
add(x,y);
}
int ans=0;
For(i,1,n) {
if(in[i]&1) add(n+1,i);
else ans++;
}
fleury();
printf("%d\n",ans);
For(i,1,n) For(j,1,n) if(b[i][j]) printf("%d %d\n",i,j);
}
return 0;
}
网络流求解混合图欧拉回路
基于流量守恒的网络流模型。
存在欧拉回路的条件是所有点入度等于出度,先将所有无向边任意定向,出度大于入度的点缺少入度,从源向该点连容量为(出度-入度)/2的边,出度小于入度的点缺少出度,从其向汇连容量为(入度-出度)/2的边,原本定向(u,v)的边连u->v容量为1,一条从源到汇流经(u,v)的流代表改变边(v,u)的方向,此时u的入度增加,v的出度增加。故当所有点连向源汇的边满流时有解,满流的中间边即为需要改变方向的边。
poj1637Sightseeing tour
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define Formylove return 0
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=4407;
typedef long long LL;
typedef double db;
using namespace std;
int n,m;
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
struct edge {
int u,v,cap,fl,nx;
edge(){}
edge(int u,int v,int cap,int fl,int nx):u(u),v(v),cap(cap),fl(fl),nx(nx){}
}e[N];
int ecnt=1,fir[N];
void add(int u,int v,int cap) {
e[++ecnt]=edge(u,v,cap,0,fir[u]); fir[u]=ecnt;
//printf("%d->%d:%d\n",u,v,cap);
e[++ecnt]=edge(v,u,0,0,fir[v]); fir[v]=ecnt;
}
queue<int>que;
int d[N];
void bfs(int s,int t) {
que.push(t);
For(i,1,n) d[i]=n;
d[t]=0;
while(!que.empty()) {
int x=que.front();
que.pop();
for(int i=fir[x];i;i=e[i].nx) {
int y=e[i].v;
if(d[y]==n&&e[i].cap==0) {
d[y]=d[x]+1;
que.push(y);
}
}
}
}
#define inf 1e9
int p[N];
int calc(int s,int t) {
int fl=inf;
for(int i=t;i!=s;i=e[p[i]].u)
fl=min(fl,e[p[i]].cap-e[p[i]].fl);
for(int i=t;i!=s;i=e[p[i]].u)
e[p[i]].fl+=fl,e[p[i]^1].fl-=fl;
return fl;
}
int c[N],cur[N];
int isap(int s,int t) {
For(i,0,n) c[i]=0;
bfs(s,t);
For(i,1,n) cur[i]=fir[i],c[d[i]]++;
int rs=0;
for(int x=s;d[x]<n;) {
if(x==t) {
rs+=calc(s,t);
x=s;
}
int ok=0;
for(int &i=cur[x];i;i=e[i].nx) if(e[i].cap>e[i].fl&&d[e[i].v]+1==d[x]) {
ok=1; p[x=e[i].v]=i; break;
}
if(!ok) {
int D=n; cur[x]=fir[x];
for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl)
D=min(D,d[e[i].v]+1);
if(!(--c[d[x]])) break;
c[d[x]=D]++;
if(x!=s) x=e[p[x]].u;
}
}
return rs;
}
int dd[N];
vector<int>vc;
void init() {
ecnt=1;
memset(fir,0,sizeof(fir));
memset(dd,0,sizeof(dd));
}
int main() {
#ifdef ANS
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int T; read(T);
while(T--) {
init();
read(n); read(m);
For(i,1,m) {
int u,v,w;
read(u); read(v); read(w);
dd[u]++; dd[v]--;
if(!w) add(u,v,1);
}
int s=n+1,t=n+2,fl=0; n+=2;
For(i,1,n-2) {
if(dd[i]&1) { fl=1; break; }
if(dd[i]>0) add(s,i,dd[i]/2);
if(dd[i]<0) add(i,t,(-dd[i])/2);
if(dd[i]!=0) vc.push_back(ecnt-1);
}
isap(s,t);
int up=vc.size();
For(i,0,up-1) if(e[vc[i]].fl!=e[vc[i]].cap) {
fl=1; break;
} vc.clear();
if(fl) puts("impossible");
else puts("possible");
}
Formylove;
}
BEST定理
拓扑排序
bzoj4010: [HNOI2015]菜肴制作
どこでもドア
求一个拓扑序使1尽可能靠前前提下2尽可能靠前然后3尽可能靠前……
直接优先队列出最小的是错的,譬如3->2,4->1,得到3,2,4,1,实际答案为4,1,3,2。
考虑如何求1最早可能出现的位置,把边反向,一直弹队列中除了1以外的元素直到没有东西可弹,此时就是1最靠后的可以出现的位置,即原图最早可能出现的位置。这次弹队列的过程中,先弹除了2的元素,同理每一次。
于是把图倒着建,优先队列弹最大的,最后倒过来即为答案。
//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=100007;
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m;
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int ecnt,fir[N],nxt[N],to[N],in[N],p[N];
void add(int u,int v) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; in[v]++;
}
priority_queue<int>que;
void tpsort() {
For(i,1,n) if(!in[i]) que.push(i);
int now=n;
while(!que.empty()) {
int x=que.top();
que.pop();
p[now--]=x;
for(int i=fir[x];i;i=nxt[i]) {
in[to[i]]--;
if(!in[to[i]]) que.push(to[i]);
}
}
if(now) puts("Impossible!");
else {
For(i,1,n-1) printf("%d ",p[i]);
printf("%d\n",p[n]);
}
}
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
read(T);
while(T--) {
read(n); read(m);
memset(fir,0,sizeof(fir));
memset(in,0,sizeof(in));
ecnt=0;
For(i,1,m) {
int x,y;
read(x); read(y);
add(y,x);
}
tpsort();
}
return 0;
}
匈牙利算法
bzoj1562: [NOI2009]变换序列
どこでもドア
求一个字典序最小的匹配。
感性理解的话,匈牙利每次都优先考虑新加的点的想法去试图调整其他点的匹配,我们倒着做,每次满足了当前后缀的一个最小字典序,然后新加进一个最前面的点,调整匹配时优先考虑这个最考前的点,从而得到现在的最小字典序 然后啊宸发现自己在扯淡,显然如果是任意的图可以随意叉掉这个算法,这道题特殊性是因为每个点只有两条出边,保证当前点优先级最高的情况下增广路是唯一的。
如果没有这个性质的话啊宸似乎不知道该怎么做。
jklover在Uoj用户群里帮我问了一下,得到了一个看起来非常正确的做法:先跑一遍匈牙利,再贪心,从小往大给每个点尝试更小的且不改变之前匹配边的匹配。
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=1e4+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,d[N],ans[N];
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int ch[N][2];
void add(int u,int v) {
if(ch[u][0]==-1) ch[u][0]=v; else ch[u][1]=v;
}
int vis[N],pr[N];
int find(int u) {
For(i,0,1) {
int y=ch[u][i];
if(y!=-1&&!vis[y]) {
vis[y]=1;
if(pr[y]==-1||find(pr[y])) {
pr[y]=u;
return 1;
}
}
}
return 0;
}
//#define DEBUG
int main() {
#ifdef DEBUG
freopen("1.in","r",stdin);
//freopen(".out","w",stdout);
#endif
read(n);
For(i,0,n-1) read(d[i]),ch[i][0]=ch[i][1]=-1;
For(i,0,n-1) {
int l=(i+d[i])%n,r=(i-d[i]+n)%n;
if(l>r) swap(l,r);
add(i,l); if(r!=l) add(i,r);
}
memset(pr,-1,sizeof(pr));
Rep(i,n-1,0) {
memset(vis,0,sizeof(vis));
if(!find(i)) { puts("No Answer"); return 0; }
}
For(i,0,n-1) ans[pr[i]]=i;
For(i,0,n-2) printf("%d ",ans[i]);
printf("%d\n",ans[n-1]);
return 0;
}