图论4:欧拉路/拓扑排序/匈牙利

欧拉路

欧拉路径:一条路径经过图中每条边一次且仅一次,点不限次。
欧拉回路:一条回路经过图中每条边一次且仅一次,点不限次。
存在欧拉路径的条件:
无向图:每个点度数都为偶,或有且仅有两个顶点的度数为奇(一个为起点一个为重点)。
有向图:每个点出度等于入度,或存在一个点出比入大一,一个点入比出大一。
存在欧拉回路的条件:
无向图:每个点度数都为偶
有向图:每个点出度等于入度

套圈法求欧拉回路

大致思路就是每次向前走到不能走,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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值