一般图的最大匹配用带花树算法:O(n^3)。具体实现我也不太清楚,但当成黑匣子用即可。
https://www.cnblogs.com/xiongtao/p/11189452.html可以参考这篇博客。
下面说下我的想法:
这题难点在于建图。
如果度数全是1,那么直接套模板就行。
但度数有不为1的情况。
回顾二分图拆点(不会的话看 lyd的算法竞赛-进阶指南上有),简单说下思路:
对于一个二分图的多重匹配:左部点i至多与kl[i]条边相连,右部点j最多与kr[i]条边相连
则这个二分图多重匹配的一个做法是——拆点:
把第i个左部节点拆成kl[i]个不同的左部节点,第j个右部节点拆成kr[j]个有部节点。//(注意原图的n+m个节点保留,这里拆的节点从n+m+1个节点编号开始),对于原图中的每条边(i,j),i连向i拆成的所有点,j连向j拆成的所有点。求解最大匹配即可。
一般图一样拆即可。
正确性:
用下上面博客里的这张图。
我们发现,对于一条边i-j, 最终的最大匹配,要么是i-j匹配,要么i与一个点匹配,j与一个点匹配。
前者说明i-j这条边不对点度数产生贡献,即最终选的子图不包括这条边。
后者恰好是i-j这条边让与i匹配的点和与j匹配的点度数都加1;
由于每个点只能被匹配一次,则只有上面两种情况,
即拆点后:每条边最多贡献一次,而左边图每个点有其度数个,若最终最大匹配是完美匹配,则这个图刚好满足每个点的度数条件。
巧妙的拆点方法。
这种遇到一次以后基本都会了。不过二分图的多重匹配一般用最大流做。。
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1050;
bool g[maxn][maxn],inque[maxn],inpath[maxn];
bool inhua[maxn];
int st,ed,newbase,ans,n;
int base[maxn],pre[maxn],match[maxn];
int head,tail,que[maxn];
int x[maxn],y[maxn],f[maxn],mp[maxn][maxn],ne,np;
void Push(int u)
{
que[tail]=u;
tail++;
inque[u]=1;
}
int Pop()
{
int res=que[head];
head++;
return res;
}
int lca(int u,int v)//寻找公共花祖先
{
memset(inpath,0,sizeof(inpath));
while(1)
{
u=base[u];
inpath[u]=1;
if(u==st) break;
u=pre[match[u]];
}
while(1)
{
v=base[v];
if(inpath[v]) break;
v=pre[match[v]];
}
return v;
}
void reset(int u)//缩环
{
int v;
while(base[u]!=newbase)
{
v=match[u];
inhua[base[u]]=inhua[base[v]]=1;
u=pre[v];
if(base[u]!=newbase) pre[u]=v;
}
}
void contract(int u,int v)//
{
newbase=lca(u,v);
memset(inhua,0,sizeof(inhua));
reset(u);
reset(v);
if(base[u]!=newbase) pre[u]=v;
if(base[v]!=newbase) pre[v]=u;
for(int i=1;i<=n;i++)
{
if(inhua[base[i]]){
base[i]=newbase;
if(!inque[i])
Push(i);
}
}
}
void findaug()
{
memset(inque,0,sizeof(inque));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)//并查集
base[i]=i;
head=tail=1;
Push(st);
ed=0;
while(head0)&&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 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 edmonds()//匹配
{
memset(match,0,sizeof(match));
for(int u=1;u<=n;u++)
{
if(match[u]==0)
{
st=u;
findaug();//以st开始寻找增广路
if(ed>0) aug();//找到增广路 重新染色,反向
}
}
}
//以上是带花树求最大匹配算法 不用看
void create()//建图
{
n=0;
memset(g,0,sizeof(g));
for(int i=1;i<=np;i++)
for(int j=1;j<=f[i];j++)
mp[i][j]=++n;//拆点,给每个度的点编号
for(int i=0;i