牛客暑期训练营(知识点)(待补充
带花树(一般图最大匹配)
知识点
1.增广路:长度为奇数,第一条和最后一条边都是未匹配的边,增光路径是一条匹配边,一条未匹配边这样交错的路径,也叫做交错路
2.二分图已经得到最大匹配当且仅当没有增广路
因为如果这时候还有增广路(长度为2*k+1),其中k条匹配的边,k+1条未匹配的边,其中第一条和最后一条是未匹配的边,我们可以把原来是未匹配的(k+1)变成匹配的,原来匹配的(k)变成未匹配的,结果增加1
所以二分图的匈牙利算法枚举左边的点集,每次若找到增广路,匹配数就+1
该原理是匈牙利算法的核心
3.寻找增广路的做法是:从一个还没被匹配的点(exposed vertex)出发,中间形成交错路径,最后停止在一个没被匹配的点,这就是一条增广路,
定义:在路径上给这些点从1开始标号,奇数的点我们称为外点,偶数的点我们称为内点,可以发现他们恰好对应两个集合(X为外点的集合,Y为内点的集合)
直接寻找增广路做法不适用于一般图匹配的原因:寻找增广路时会形成环,导致有些点既是内点又是外点。一些题解的说法是二分图中会形成环,但环的边是偶数的,不影响,而在一般图中形成的环是奇数的。
这样说是为了解释一般图为什么不合适,实际上在二分图的操作中是不会出现环的,如果出现,说明开始搜索的起点不是一个未匹配的点或者原来的匹配方式有错误(存在一个点存在于两个匹配中)
二分图的特殊性决定了寻找增广路的过程中,外点、内点、外点、内点……这样的分布能对应X集合、Y集合、X集合、Y集合……
但一般图中存在从X集合到X集合的边和从Y集合到Y集合的边,若直接寻找增广路会有些点既是内点又是外点,如下图,v既是内点又是外点,所以才会有缩点等一系列操作,都是为了缩点后能继续用增广路的做法
你会问,既是外点又是内点又怎样呢?
找到增广路时都会把未匹配边变成匹配边,匹配边变成未匹配边,若是有些点既是外点又是内点,会匹配出错,即一个点存在于两个匹配中
例题 ZOJ 3316(一般图的完美匹配)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 402
int base[N],pre[N],match[N],que[N],inque[N],inpath[N],inblossom[N],front,rear;
int n,x[N],y[N],L,g[N][N];
int findancestor(int u,int v)
{
memset(inpath,0,sizeof(inpath));
while(1)
{
u=base[u];
inpath[u]=1;
if(match[u]==-1) break;
u=pre[match[u]];
}
while(1)
{
v=base[v];
if(inpath[v]) return v;
v=pre[match[v]];
}
}
void reset_trace(int u,int anc)
{
while(u!=anc)
{
int v=match[u];
inblossom[base[u]]=1;
inblossom[base[v]]=1;
v=pre[v];
if(base[v]!=anc) pre[v]=match[u];
u=v;
}
}
void contract(int u,int v)
{
int anc=findancestor(u,v);
memset(inblossom,0,sizeof(inblossom));
reset_trace(u,anc);
reset_trace(v,anc);
if(base[u]!=anc) pre[u]=v;
if(base[v]!=anc) pre[v]=u;
for(int i=1;i<=n;++i)
if(inblossom[base[i]])
{
base[i]=anc;
if(!inque[i])
{
inque[i]=1;
que[rear++]=i;
}
}
}
bool bfs(int st)
{
front=rear=0;
for(int i=1;i<=n;++i) pre[i]=-1,inque[i]=0,base[i]=i;
que[rear++]=st;inque[st]=1;
while(front<rear)
{
int u=que[front++];
for(int v=1;v<=n;++v)
if(g[u][v]&&base[u]!=base[v]&&match[u]!=v)
{
if(v==st||(match[v]!=-1&&pre[match[v]]!=-1)) // circle
contract(u,v);
else if(pre[v]==-1)
{
pre[v]=u;
if(match[v]!=-1)
que[rear++]=match[v],inque[match[v]]=1;
else
{
u=v;
while(u!=-1)
{
v=pre[u];
int w=match[v];
match[u]=v;match[v]=u;
u=w;
}
return true;
}
}
}
}
return false;
}
int num[N],sz,vis[N],pp[N];
void dfs(int u)
{
vis[u]=sz;num[sz]++;
for(int i=1;i<=n;++i)
if(g[u][i]&&!vis[i])
dfs(i);
}
bool solve()
{
for(int i=1;i<=n;++i)
if(match[i]==-1)
bfs(i);
for(int i=1;i<=n;++i)
if(match[i]!=-1)
pp[vis[i]]++;
for(int i=1;i<=sz;++i)
if(pp[i]!=num[i])
return false;
return true;
}
void init()
{
memset(g,0,sizeof(g));
memset(match,-1,sizeof(match));
}
int abs(int a)
{return a>0?a:-a;}
int main ()
{
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=1;i<=n;++i)
scanf("%d%d",&x[i],&y[i]);
scanf("%d",&L);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(abs(x[i]-x[j])+abs(y[i]-y[j])<=L)
{
g[i][j]=g[j][i]=1;
}
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
memset(pp,0,sizeof(pp));
sz=0;
for(int i=1;i<=n;++i)
if(!vis[i])
{
sz++;
dfs(i);
}
if(solve()) printf("YES\n");
else printf("NO\n");
}
return 0;
}
思路:根据曼哈顿距离可以连接点,形成一个n个联通块的图,后手要赢,必须先手拿走的棋之后 有相连的棋(匹配的棋)。所以只要每个连通块的棋都是完美匹配的,后手拿先手匹配的棋,一定能赢,即只要整个图是完美匹配就行,这样每个联通块一定是完美匹配。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=400;
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],a[maxn],b[maxn];
int dis(int a1,int b1,int a2,int b2)
{
return abs(a1-a2)+abs(b1-b2);
}
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(head<tail)
{
int u=Pop();
for(int v=1;v<=n;v++)
{
if(g[u][v]&&(base[u]!=base[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 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 print()
{
ans=0;
for(int u=1;u<=n;u++)
if(match[u]>0)
ans++;
if(ans==n)
printf("YES\n");
else
printf("NO\n");
}
int main()
{
int l;
while(scanf("%d",&n)!=EOF)
{
ans=0;
memset(g,0,sizeof(g));
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&b[i]);
scanf("%d",&l);
for(int i=1;i<=n;i++)//建图
{
for(int j=i+1;j<=n;j++)
{
if(dis(a[i],b[i],a[j],b[j])<=l)
g[i][j]=g[j][i]=1;
}
}
edmonds();//匹配
for(int i=1;i<=n;i++)
if(match[i]!=0)
ans++;
if(ans==n)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}