图论:一般图最大匹配(带花树)
模板:直接求一般图最大匹配:
同时求出match[i]表示与第i个节点匹配的点是哪一个
点击查看折叠代码块
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int inf=0x3f3f3f3f;
typedef long long ll;
int n,m;
int mp[maxn][maxn];
int f[maxn];//子图中每个点需要达到的度数
int x[maxn],y[maxn];//原图的边对应的两点
int deg[maxn];
int fa[maxn];//并查集
int pre[maxn],match[maxn];
int st,ed,newfa,ans,ct=0;
bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
bool inhua[maxn];//点是否在花内
int head,tail;
int que[maxn];
void _push(int x){
que[tail++]=x;
}
int _pop(){
int x=que[head++];
return x;
}
int lca(int u,int v){//朴素法找最近公共祖先
memset(inpath,0,sizeof(inpath));
while(1){
u=fa[u];//u变成u的祖先
inpath[u]=1;
if(u==st) break;
u=pre[match[u]];
}
while(1){
v=fa[v];
if(inpath[v]) break;
v=pre[match[v]];
}
return v;
}
void reset(int u){//缩环
int v;
while(fa[u]!=newfa){
v=match[u];
inhua[fa[u]]=inhua[fa[v]]=1;
u=pre[v];
if(fa[u]!=newfa) pre[u]=v;
}
}
void contract(int u,int v){
newfa=lca(u,v);
memset(inhua,0,sizeof(inhua));
reset(u);
reset(v);
if(fa[u]!=newfa) pre[u]=v;
if(fa[v]!=newfa) pre[v]=u;
for (int i=1;i<=ct;i++){
if(inhua[fa[i]]){
fa[i]=newfa;
if(!inque[i]){
inque[i] = 1;
_push(i);
}
}
}
}
void aug(){
int u,v,w;
u=ed;
while(u>0){
v=pre[u];
w=match[v];
match[v]=u;
match[u]=v;
u=w;
}
}
void findaug(){//找增广路
memset(inque,0,sizeof(inque));
memset(pre,0,sizeof(pre));
for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集
head=tail=1;
_push(st);
ed=0;
while(head<tail){
int u=_pop();
for (int v=1;v<=ct;v++){
if(g[u][v] && (fa[u]!=fa[v]) && match[u]!=v)//如果两个点之间有边,两个点父亲不同且两个点之间不是匹配
{
if(v==st || (match[v]>0) && pre[match[v]]>0) //成环了
contract(u,v);
else if(pre[v] == 0){
pre[v]=u;
if(match[v]>0) _push(match[v]);
else{
ed=v;
return ;
}
}
}
}
}
}
void edmonds(){//带花树,求匹配
memset(match,0,sizeof(match));
for (int u=1;u<=ct;u++){//对于新图中每个点
if(match[u]==0){//如果该点未匹配过
st=u;
findaug();//以st开始寻找增广路
if(ed>0) aug();//找到增广路,重新染色,反向
}
}
}
//以上是带花树求最大匹配算法,可以作为一个板子
int main(){
ct = 0;
cin>>n>>m;
for (int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
g[u][v] = g[v][u] = 1;
}
ct = n;//ct记录的是图点的总个数,根据题意可能需要拆点
edmonds();
ans = 0;
for (int i=1;i<=ct;i++){
if(match[i]!=0){
// cout<<"i = "<<i<<" match = "<<match[i]<<endl;
ans++;
}
}
int Maxmatch = ans/2;
cout<<Maxmatch<<endl;
return 0;
}
/*
6 6
1 2
2 3
3 4
4 5
1 5
4 6
i = 1 match = 5
i = 2 match = 3
i = 3 match = 2
i = 4 match = 6
i = 5 match = 1
i = 6 match = 4
3
*/
例题:hdu3551
点击查看折叠代码块
/*
2020-7-14
hdu-3551,子图点度数为任意
一般图匹配_Edmond's Algorithm
建图:将每个点根据f[i]拆成f[i]个点。
将每一条边拆成两个点,将这条边关联的两个点的拆点分别与边的拆点相连,还有边的拆点也要相连:
如 1-2 这条边,假设f[1]=1,f[2]=2,
则我们将1号点拆成点1,2号点拆成点2和点3,1-2这条边拆成两个点4和点5,将点1与点4相连,点2和点3都与点5相连,点4与点5相连
建图完成后,对这个图跑带花树找到匹配,如果这个匹配是完美匹配,则说明存在子图。
参考:https://www.cnblogs.com/xiongtao/p/11189452.html
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int inf=0x3f3f3f3f;
typedef long long ll;
int n,m;
int mp[maxn][maxn];
int f[maxn];//子图中每个点需要达到的度数
int x[maxn],y[maxn];//原图的边对应的两点
int deg[maxn];
int fa[maxn];//并查集
int pre[maxn],match[maxn];
int st,ed,newfa,ans,ct=0;
bool g[maxn][maxn],inque[maxn],inpath[maxn];//新图的邻接矩阵,是否在队列中,是否在增广路中
bool inhua[maxn];//点是否在花内
int head,tail;
int que[maxn];
void _push(int x){
que[tail++]=x;
}
int _pop(){
int x=que[head++];
return x;
}
int lca(int u,int v){//朴素法找最近公共祖先
memset(inpath,0,sizeof(inpath));
while(1){
u=fa[u];//u变成u的祖先
inpath[u]=1;
if(u==st) break;
u=pre[match[u]];
}
while(1){
v=fa[v];
if(inpath[v]) break;
v=pre[match[v]];
}
return v;
}
void reset(int u){//缩环
int v;
while(fa[u]!=newfa){
v=match[u];
inhua[fa[u]]=inhua[fa[v]]=1;
u=pre[v];
if(fa[u]!=newfa) pre[u]=v;
}
}
void contract(int u,int v){
newfa=lca(u,v);
memset(inhua,0,sizeof(inhua));
reset(u);
reset(v);
if(fa[u]!=newfa) pre[u]=v;
if(fa[v]!=newfa) pre[v]=u;
for (int i=1;i<=ct;i++){
if(inhua[fa[i]]){
fa[i]=newfa;
if(!inque[i]){
inque[i] = 1;
_push(i);
}
}
}
}
void aug(){
int u,v,w;
u=ed;
while(u>0){
v=pre[u];
w=match[v];
match[v]=u;
match[u]=v;
u=w;
}
}
void findaug(){//找增广路
memset(inque,0,sizeof(inque));
memset(pre,0,sizeof(pre));
for(int i=1;i<=ct;i++) fa[i]=i;//初始化并查集
head=tail=1;
_push(st);
ed=0;
while(head<tail){
int u=_pop();
for (int v=1;v<=ct;v++){
if(g[u][v] && (fa[u]!=fa[v]) && match[u]!=v)//如果两个点之间有边,两个点父亲不同且两个点之间不是匹配
{
if(v==st || (match[v]>0) && pre[match[v]]>0) //成环了
contract(u,v);
else if(pre[v] == 0){
pre[v]=u;
if(match[v]>0) _push(match[v]);
else{
ed=v;
return ;
}
}
}
}
}
}
void edmonds(){//带花树,求匹配
memset(match,0,sizeof(match));
for (int u=1;u<=ct;u++){//对于新图中每个点
if(match[u]==0){//如果该点未匹配过
st=u;
findaug();//以st开始寻找增广路
if(ed>0) aug();//找到增广路,重新染色,反向
}
}
}
//以上是带花树求最大匹配算法,可以作为一个板子
void create(){//建图
ct=0;
memset(g,0,sizeof(g));
for (int i=1;i<=n;i++){//对于每个点的度数进行拆点
for (int j=1;j<=f[i];j++){
mp[i][j] = ++ct;//原图中点i拆成f[i]个点的编号
}
}
for (int i=1;i<=m;i++){//对于每条边拆成两个点,ct+1拆成x,ct+2拆成y
for (int j=1;j<=f[x[i]];j++){//点拆点和边拆点相连,这条边的第一个点为x[i],f[x[i]]表示拆成的点的个数,mp[x[i]][j]表示x[i]这个点拆成的第j个点的编号
g[ mp[x[i]][j] ][ct+1] = g[ct+1][ mp[x[i]][j] ]=1;
}
for (int j=1;j<=f[y[i]];j++){//同上
g[ mp[y[i]][j] ][ct+2] = g[ct+2][ mp[y[i]][j] ]=1;
}
g[ct+1][ct+2]=g[ct+2][ct+1]=1;//边拆点相连
ct+=2;
}
}
void print(){
ans=0;
for (int i=1;i<=ct;i++){//看匹配中点数
if(match[i]!=0){//如果这个点被匹配到了
ans++;
}
}
if(ans==ct){//如果匹配是一个完美匹配
printf("YES\n");
}
else printf("NO\n");
}
int main(){
int t,k=0;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d",&x[i],&y[i]);
}
for (int i=1;i<=n;i++){
scanf("%d",&f[i]);
}
printf("Case %d: ",++k);
create();
edmonds();
print();
}
return 0;
}