二分图的匹配
最大匹配
Graphs<MaxN,MaxM>G;
Queue<int,MaxN>Q;
int ans;
int Pre[MaxN],Match[MaxN],Mark[MaxN];
//Mark 为时间戳 每一个左部点的匹配 右部点只会参加一次
inline void Augment(int from){
int tmp;
while(from!=-1){
tmp=Match[Pre[from]];
Match[Pre[from]]=from;
Match[from]=Pre[from];
from=tmp;
}
}
//Mark 为时间戳 每一个左部点的匹配 右部点只会参加一次
int n,m;
inline bool Bfs(int start){
Pre[start]=-1;
Q.Clear();Q.Push(start);
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(register int k=G.Last[from];k!=-1;k=G.Next[k]){
if(Mark[G.To[k]]==start)continue;
Mark[G.To[k]]=start;//标记一下
Pre[G.To[k]]=from;
//这里把G.To[k]加入到交错路中
if(Match[G.To[k]]==-1){
Augment(G.To[k]);
return true;
}
else{
Q.Push(Match[G.To[k]]);//可扩展加入队列
//到时候这玩意儿更新了,顺带把from也更新
}
}
}
return false;
}
inline void ReInit(){
memset(Match,-1,sizeof(Match));
memset(Mark,-1,sizeof(Mark));
}
inline void Work(){
Insert(G,x,y+n);//左部点的编号和右部点的编号分开
for(register int i=1;i<=n;i++)
if(Match[i]==-1)
ans+=Bfs(i);
}
最小点覆盖 / / /最大独立集
二分图中 : : :
最小点覆盖 = = = 最大匹配数 = = = n − n- n− 最大独立集 . . .
最小点覆盖是指找到一个最小的点集 , , , 使得与这些集合中的点相连的边覆盖 G G G 中的所有边 . . .
证明 : : :
- 最小点覆盖大于等于最大匹配
假设u和v是最大匹配中 , , , 某一条匹配边的两个端点 , , , 则u和v不可能同时与非匹配点有边相连 , , , 否则最大匹配数目至少要增加 1 1 1 , , , 矛盾 . . . 即u和v最多只有一个点可以与若干条非匹配点相连接 , , , 因此 , , , 只需要选择有连接非匹配点的点进入最小点覆盖集合就可以 . . . 证明完毕 . . .
- 最大独立集 = = = 点数 − - − 最大匹配
证明 : : :
假设已经得到最大匹配 , , , 在某条匹配边(u,v)中 , , , u有与之相连的非匹配点(那么v一定没有了) , , , 去掉所有匹配边的u这一点 , , , 得到图M,M中的点一定是独立的点集合 , , , 同时给M增加任意一个已经去掉的点都会得到一条匹配边 , , , 破坏独立性 , , , 所以 , , , 最大独立集 = = = 点数 − - − 最大匹配 , , , 证明完毕 . . .
F r o m From From zjck1995
二分图的多重匹配
使用网络流算法解决 . . .
最大权匹配
const long long Inf=0x3f3f3f3f3f3f3f3f;
const int MaxV1=510*2,MaxV2=510*2;
long long Weight[MaxV1][MaxV2];
long long Expect1[MaxV1];int Match1[MaxV1];
long long Expect2[MaxV2];int Match2[MaxV2];
int Pre[MaxV2];long long Slack[MaxV2];
int Visit1[MaxV1],Visit2[MaxV2];
int sizev1,sizev2;
inline void Augment(int from){
int tmp;
while(from!=-1){
tmp=Match1[Pre[from]];
Match1[Pre[from]]=from;
Match2[from]=Pre[from];
from=tmp;
}
}
Queue<int,MaxV1>Q;
inline long long Calc(int from,int to){
return Expect1[from]+Expect2[to]-Weight[from][to];
}
inline bool KuhnMunkres(int start){
Pre[start]=-1;
memset(Slack,0x3f,sizeof(Slack));
Q.Clear();Q.Push(start);
while(true){
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
Visit1[from]=start;
for(register int to=1;to<=sizev2;to++){
if(Visit2[to]==start)continue;
if(Calc(from,to)<Slack[to]){
Slack[to]=Calc(from,to);
Pre[to]=from;
if(Slack[to]==0){
Visit2[to]=start;
if(Match2[to]==-1){
Augment(to);
return true;
}
else Q.Push(Match2[to]);
}
}
}
}
long long delta=Inf;
for(register int i=1;i<=sizev2;i++)
if(Visit2[i]!=start)delta=Min(delta,Slack[i]);
for(register int i=1;i<=sizev1;i++)
if(Visit1[i]==start)Expect1[i]-=delta;
for(register int i=1;i<=sizev2;i++)
if(Visit2[i]==start)Expect2[i]+=delta;
else Slack[i]-=delta;
for(register int i=1;i<=sizev2;i++)
if(Visit2[i]!=start&&Slack[i]==0){
Visit2[i]=start;
if(Match2[i]==-1){
Augment(i);
return true;
}
else Q.Push(Match2[i]);
}
}
}
inline void ReInit(){
memset(Weight,0,sizeof(Weight));
memset(Match1,-1,sizeof(Match1));
memset(Match2,-1,sizeof(Match2));
memset(Expect1,-0x3f,sizeof(Expect1));
memset(Expect2,0,sizeof(Expect2));
}
int n,m,k;
int main(){
ReInit();
scanf("%d%d%d",&n,&m,&k);
sizev1=n,sizev2=Max(m,n);
for(register int i=1,x,y;i<=k;i++){
long long w;
scanf("%d%d%lld",&x,&y,&w);
Weight[x][y]=w;
Expect1[x]=Max(Expect1[x],w);
}
for(register int i=1;i<=n;i++)
KuhnMunkres(i);
long long ans=0;
for(register int i=1;i<=n;i++)
ans+=Weight[i][Match1[i]];
printf("%lld\n",ans);
for(register int i=1;i<=n;i++)
printf("%d ",(Match1[i]==-1||Match1[i]>m||Weight[i][Match1[i]]==0)?0:Match1[i]);
return 0;
}
特殊情况 : : :
-
通过为右部点添加虚点来保证左部点个数小于等于右部点个数 . . . ( ( ( 是必要的 , , , K m Km Km 需要在完备匹配中保证正确 ) ) )
-
然后 , , , 我们要将原本不存在的边连成虚边 . . .
-
对于需要特判无解的题目 , , , 将虚边边权设为 − I n f -Inf −Inf , , , 如果被迫选了 − I n f -Inf −Inf 的边就输出无解 . . . ( ( ( 实现上就是先跑 K m Km Km , , , 然后在统计答案时判断有没有虚边 ) ) )
-
对于允许非完美匹配的题目 , , , 将虚边边权设为 0 0 0 即可 . . .
-
( ( ( 对于无解 , , , 也可以选择加一个虚点作为退路 , , , 跑一遍 K m Km Km , , , 如果该虚点被匹配则必然无解 ) ) )
F r o m From From Singercoder . . .
一般图匹配
最大匹配
const int MaxN=1e3+1e2;
const int MaxM=2e5+6e4;
Graphs<MaxN,MaxM>G;
int Dad[MaxN];
inline int GetDad(int x){
return x==Dad[x]?x:Dad[x]=GetDad(Dad[x]);
}
int Visit[MaxN],Pre[MaxN],Match[MaxN],Mark[MaxN];
inline int FindLca(int x,int y)
{
static int tim=0;
tim++;
while(x!=0){
x=GetDad(x);
Visit[x]=tim;
x=Pre[Match[x]];
}
while(y!=0){
y=GetDad(y);
if(Visit[y]==tim)return y;
y=Pre[Match[y]];
}
return 0;
}
Queue<int,MaxN>Q;
inline void Group(int x,int lca)
{
while(x!=lca){
int y=Match[x],z=Pre[y];
if(GetDad(z)!=lca)Pre[z]=y;//这不是花根 把路径赋值
if(Mark[y]==2)Q.Push(y),Mark[y]=1;//变成一类点更新
if(Mark[z]==2)Q.Push(z),Mark[z]=1;
Dad[x]=y;Dad[y]=z;
x=z;
}
}
//Group是造花的过程
int sizev;
inline bool Bfs(int start)
{
for(register int i=1;i<=sizev;i++)
Dad[i]=i,Pre[i]=0,Mark[i]=0;
Mark[start]=1;Q.Clear();Q.Push(start);
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(register int k=G.Last[from];k!=-1;k=G.Next[k]){
register int lx=GetDad(from),ly=GetDad(G.To[k]);
if(Match[from]==G.To[k]||lx==ly||Mark[G.To[k]]==2)continue;
else if(Mark[G.To[k]]==1){
register int lca=FindLca(from,G.To[k]);
if(lx!=lca)Pre[from]=G.To[k];
if(ly!=lca)Pre[G.To[k]]=from;
Group(from,lca);Group(G.To[k],lca);
}
else if(Mark[G.To[k]]==0){
if(Match[G.To[k]]==0){
int x=from,y=G.To[k];
while(y!=0){
int z=Match[x];
Match[y]=x;Match[x]=y;
y=z;x=Pre[y];
}
return true;
}
else{
Pre[G.To[k]]=from;
Q.Push(Match[G.To[k]]);
Mark[Match[G.To[k]]]=1;
Mark[G.To[k]]=2;
}
}
}
}
return false;
}
int n,m,ans;
int main()
{
Read(n);Read(m);
sizev=n;
for(int i=1,x,y;i<=m;i++)
{
Read(x);Read(y);
Insert(G,x,y);
Insert(G,y,x);
}
ans=0;
for(int i=1;i<=n;i++)
if(Match[i]==0&&Bfs(i))ans++;
printf("%d\n",ans);
for(register int i=1;i<=n;i++)
printf("%d ",Match[i]);
return 0;
}
最大独立集
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn=410;
int None[Maxn][Maxn],All[Maxn][Maxn],Some[Maxn][Maxn],Deg[Maxn];
bool Map[Maxn][Maxn];int ans;
inline void FindGraph(int pos,int al,int so,int no)
{
if(al+so<=ans)return ;
if(so==0&&no==0){ans=al;return ;}
int pi=0;
if(so){
pi=Some[pos][1];
for(int i=1;i<=al;i++)All[pos+1][i]=All[pos][i];
}
for(register int i=1;i<=so;i++){
int now=Some[pos][i];
if(!Map[pi][now])continue;
int nno=0,nso=0;
for(register int j=1;j<=so;j++)
if(!Map[now][Some[pos][j]])Some[pos+1][++nso]=Some[pos][j];
for(register int j=1;j<=no;j++)
if(!Map[now][None[pos][j]])None[pos+1][++nno]=None[pos][j];
All[pos+1][al+1]=now;
FindGraph(pos+1,al+1,nso,nno);
Some[pos][i]=0;None[pos][++no]=now;
}
}
bool Cmp(int a,int b) {return Deg[a]>Deg[b];}
int n,m;
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++){
Deg[i]=n-1;
Some[0][i]=i;
Map[i][i]=Map[0][i]=Map[i][0]=true;
}
for(register int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
Map[x][y]=Map[y][x]=true;
Deg[x]--;Deg[y]--;
}
sort(Some[0]+1,Some[0]+n+1,Cmp);
Find_Graph(0,0,n,0);
printf("%d\n",ans);
return 0;
}
B r o n − K e r b o s c h Bron-Kerbosch Bron−Kerbosch 算法
该算法本质也是 D F S DFS DFS . . . 引入三个集合 , , , a l l all all 集合 , , , s o m e some some 集合 , , , n o n e none none 集合 , , , 其中 s o m e some some 集合代表待检查且可能能加入团的节点 , , , n o n e none none 集合代表已经检查过且我们认为不能加入团的节点 , , , a l l all all 则是检查过并且我们认为能加入最大团的节点 , , , 当 s o m e some some 集合和 n o n e none none 集合都为空的时候 , , , a l l all all 集合即为我们需要求的极大团 . . . 每次我们检查 s o m e some some 集合里的一个节点 , , , 并把它加入 a l l all all 集合 , , , 那么可能加入待检查序列的就是它自己的邻接点 ( ( ( 毕竟要保证团内所有点都有边相连 ) ) ) 和 s o m e some some 集合做一个交集 , , , n o n e none none 集合同样和邻接点们作交集 , , , 以保证 s o m e some some 和 n o n e none none 里面的点和 a l l all all 里的点都有边相连 . . . 把当前检查点从 a l l all all 里拿出来以后就放入 n o n e none none 里面 , , , 再从 s o m e some some 集合的下一个开始检查 , , , s o m e some some 集合为空的时候就没有可以加入 a l l all all 集合的点了 , , , s o m e some some 集合为空且 n o n e none none 集合不为空的时候就代表 n o n e none none 集合里面的点放进 a l l all all 集合团会更大 ( ( ( 之前检查过这种情况 ) ) ) 所以一定要两个集合都为空才行 . . .
这里还要介绍一种优化方法
,
,
, 因为不加这个优化可能连板子题都会
T
L
E
.
TLE.
TLE. 如果我们要从
s
o
m
e
some
some 里选一个点
p
i
v
o
t
pivot
pivot 加入到
a
l
l
all
all 里的话
,
,
, 它的邻接点势必会因为和
s
o
m
e
some
some 作交集而留在
s
o
m
e
some
some 里待检查
,
,
, 在下一层
D
F
S
DFS
DFS 里势必会被检查
,
,
, 所以没有必要在检查
p
i
v
o
t
pivot
pivot 的这一层里再以它们为起点去检查
,
,
, 这会导致重复
.
.
. 选
p
i
v
o
t
pivot
pivot 的时候也可以以度数的大小选度数最大的点
,
,
, 这样能减少的检查次数也最多
.
.
.
F
r
o
m
From
From SparkFucker
最大权匹配
struct Edges{
int from,to,data;
Edges(int _from=0,int _to=0,int _data=0):
from(_from),to(_to),data(_data){}
};Edges Eij[MaxV][MaxV];
/*
这里的边使得带花树中的花更方便
的以一个点的身份执行操作;
*/
int Match[MaxV],Pre[MaxV],Mark[MaxV];
Queue<int,MaxV*MaxV>Q;
vector<int>Leaf[MaxV];
//Leaf[i][0]是这个花的根
int Root[MaxV][MaxV],Slack[MaxV],Expect[MaxV];
int Visit[MaxV],Dad[MaxV],sizev;
inline int GetLca(int x,int y){
static int tim=0;
if(tim==MaxInt){
tim=0;
memset(Visit,0,sizeof(Visit));
}
tim++;
while(x!=0){
Visit[x]=tim;
x=Dad[Match[x]];
if(x!=0)x=Dad[Pre[x]];
}
while(y!=0){
if(Visit[y]==tim)return y;
y=Dad[Match[y]];
if(y!=0)y=Dad[Pre[y]];
}
return 0;
}
/*
还没能搞明白Pre的含义?
*/
inline int CalcEdge(const Edges&e){
return Expect[e.from]+Expect[e.to]-Eij[e.from][e.to].data*2;
}
/*
计算顶标值
*/
inline void Update(int id,int now){
if(!Slack[now]||CalcEdge(Eij[id][now])<CalcEdge(Eij[Slack[now]][now]))
Slack[now]=id;
}
/*
这里的Slack是指使now匹配的最小增广花费的点
和Km算法里的差不多,但Km里只保存了值
*/
inline void CalcSlack(int now){
Slack[now]=0;
for(register int i=1;i<=sizev;i++)
if(Eij[i][now].data>0&&Dad[i]!=now&&Mark[Dad[i]]==0)
Update(i,now);
}
/*
这里是更新Slack的函数
第二个判断是不是在一朵花里
第三个还没有搞明白?
*/
inline void Join(int now){
if(now<=sizev)return Q.Push(now);
int sizel=Leaf[now].size();
for(register int i=0;i<sizel;i++)
Join(Leaf[now][i]);
}
/*
一朵花的增广路要有奇环中的点来寻找
若编号小于sizev,说明now是原图中的点
反之,now为一朵花
*/
inline void SetDad(int now,int dad){
Dad[now]=dad;if(now<=sizev)return;
int sizel=Leaf[now].size();
for(register int i=0;i<sizel;i++)
SetDad(Leaf[now][i],dad);
}
/*
并查集操作
*/
inline void SetMatch(int from,int to){
Match[from]=Eij[from][to].to;
//和一个东西匹配的一定是点
if(from<=sizev)return ;
//下面是奇环的操作
int root=Root[from][Eij[from][to].from];//Root还没能搞明白? 先看下面的 BlossomBuild
int place=find(Leaf[from].begin(),Leaf[from].end(),root)-Leaf[from].begin();
//这句话就是找root的位置
if(place%2==1){
reverse(Leaf[from].begin()+1,Leaf[from].end());
place=(int)Leaf[from].size()-place;
//这里还没有搞懂 ? 看下面的图解
}
//交错路更换一遍
for(register int i=0;i<place;i++)
SetMatch(Leaf[from][i],Leaf[from][i^1]);
//现在奇环的匹配权在root上,把他当成根
SetMatch(root,to);
rotate(Leaf[from].begin(),Leaf[from].begin()+place,Leaf[from].end());
}
inline void BlossomBreak(int now){
int sizel=Leaf[now].size();
for(register int i=0;i<sizel;i++){
if(Leaf[now][i]>sizev&&!Expect[Leaf[now][i]])
BlossomBreak(Leaf[now][i]);
//这里让Expect为零的花炸开,应该是没法匹配了
else SetDad(Leaf[now][i],Leaf[now][i]);
//让有志向的编号独立
}
Dad[now]=0;//回收节点
}
int tot;
inline void BlossomBuild(int from,int lca,int to){
int now=sizev+1;while(now<=tot&&Dad[now])now++;if(now>tot)tot++;
//这里是一个无脑找新编号的语句,可以自己打垃圾桶
Expect[now]=Mark[now]=0;
Match[now]=Match[lca];
Leaf[now].clear();
Leaf[now].push_back(lca);
//初始化和特性的维护
for(register int k=from;k!=lca;k=Dad[Pre[Dad[Match[k]]]])
Leaf[now].push_back(k),Leaf[now].push_back(Dad[Match[k]]),Join(Dad[Match[k]]);
//这里还没有完全理解 主要要理解各个数组之间的关系 (上面的问号也是)
//Dad是并查集,指这个编号属于哪个集合(花)
//Pre就看下面的DealEdge吧
reverse(Leaf[now].begin()+1,Leaf[now].end());
//形成一条链
for(register int k= to ;k!=lca;k=Dad[Pre[Dad[Match[k]]]])
Leaf[now].push_back(k),Leaf[now].push_back(Dad[Match[k]]),Join(Dad[Match[k]]);
SetDad(now,now);
//下面是和其他编号关系
for(register int i=1;i<=tot;i++)
Eij[now][i].data=Eij[i][now].data=0,Root[now][i]=0;
int sizel=Leaf[now].size();
for(register int i=0;i<sizel;i++){
int vex=Leaf[now][i];
for(register int j=1;j<=tot;j++)
if(!Eij[now][j].data||CalcEdge(Eij[vex][j])<CalcEdge(Eij[now][j]))
Eij[now][j]=Eij[vex][j],Eij[j][now]=Eij[j][vex];
//类似于更新Slack,花的边由节点来决定
for(register int j=1;j<=tot;j++)
if(Root[vex][j])Root[now][j]=vex;
//从这里貌似能看出Root指连接点的关系
}
CalcSlack(now);
}
inline void Group(int x,int y){
while(true){
int z=Dad[Match[x]];
SetMatch(x,y);
if(z==0)return ;
SetMatch(z,Dad[Pre[z]]);
x=Dad[Pre[z]];y=z;
}
}
inline int DealEdge(const Edges&e){
int from=Dad[e.from],to=Dad[e.to];
//我们要的是花朵的编号
if(Mark[to]==-1){
Pre[to]=e.from;//Pre指的还是原图中的节点
Mark[to]=1;Mark[Dad[Match[to]]]=0;
Slack[to]=Slack[Dad[Match[to]]]=0;
Join(Dad[Match[to]]);
}
else if(!Mark[to]){
int lca=GetLca(from,to);
if(!lca){
Group(from,to);Group(to,from);
for(register int i=sizev+1;i<=tot;i++)
if(Dad[i]==i&&Expect[i]==0)
BlossomBreak(i);
return true;
/*
跑完这轮之后就要开新一轮的匹配了,
所以把没有欲望的花给删了.
*/
}
else BlossomBuild(from,lca,to);
}
return false;
}
inline void BlossomDelete(int now){
//与BlossomBreak不同的是只拆一个花
int sizel=Leaf[now].size();
for(register int i=0;i<sizel;i++)
SetDad(Leaf[now][i],Leaf[now][i]);
int root=Root[now][Eij[now][Pre[now]].from];//这是根和Pre[now]的链接,看上面Pre的更新
int place=find(Leaf[now].begin(),Leaf[now].end(),root)-Leaf[now].begin();
if(place%2==1){
reverse(Leaf[now].begin()+1,Leaf[now].end());
place=(int)Leaf[now].size()-place;
}
for(register int i=0;i<place;i+=2){
int from=Leaf[now][i],to=Leaf[now][i+1];
Pre[from]=Eij[to][from].from;//to在前面,指to的一边
Mark[from]=1;Mark[to]=0;
Slack[from]=0;CalcSlack(to);Join(to);
}
Mark[root]=1;Pre[root]=Pre[now];
for(register int i=place+1;i<sizel;i++){//这里的还没搞懂
int from=Leaf[now][i];
Mark[from]=-1;CalcSlack(from);
}
Dad[now]=0;
}
inline bool Work(){
Q.Clear();
for(int i=1;i<=tot;i++)Slack[i]=0,Mark[i]=-1;
for(register int i=1;i<=tot;i++)
if(Dad[i]==i&&!Match[i])
Slack[i]=Pre[i]=Mark[i]=0,Join(i);
if(Q.Empty())return false;
while(true){
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(register int to=1;to<=sizev;to++){
if(Eij[from][to].data>0&&Dad[from]!=Dad[to]){
if(!CalcEdge(Eij[from][to])){
if(DealEdge(Eij[from][to]))
return true;
}
else if(Mark[Dad[to]]!=1)
Update(from,Dad[to]);
}
}
}
int delta=Inf;
for(register int i=1;i<=sizev;i++)
if(!Mark[Dad[i]])
delta=Min(delta,Expect[i]);
for(register int i=sizev+1;i<=tot;i++)
if(Dad[i]==i&&Mark[i]==1)
delta=Min(delta,Expect[i]/2);//未理解 ?
for(register int i=1;i<=tot;i++)
if(Dad[i]==i&&Slack[i]!=0)
if(Mark[i]==-1)delta=Min(delta,CalcEdge(Eij[Slack[i]][i]));
else if(Mark[i]==0)delta=Min(delta,CalcEdge(Eij[Slack[i]][i])/2);
else{}
else{}
for(register int i=1;i<=sizev;i++)
if(Mark[Dad[i]]==0)Expect[i]-=delta;
else if(Mark[Dad[i]]==1)Expect[i]+=delta;
for(register int i=sizev+1;i<=tot;i++)
if(Dad[i]==i)
if(Mark[i]==0)Expect[i]+=delta*2;
else if(Mark[i]==1)Expect[i]-=delta*2;
else{}
else{}
for(register int i=1;i<=sizev;i++)
if(!Expect[i])return false;//连点都没欲望了吗,跟你说再见
for(register int i=1;i<=tot;i++)
if(Dad[i]==i&&Slack[i]&&Dad[Slack[i]]!=i&&CalcEdge(Eij[Slack[i]][i])==0)
if(DealEdge(Eij[Slack[i]][i]))return true;
for(register int i=sizev+1;i<=tot;i++)
if(Dad[i]==i&&Mark[i]==1&&!Expect[i])
BlossomDelete(i);
}
return false;
}
int n,m;
int main(){
scanf("%d%d",&n,&m);
sizev=n;
int maxi=0;
for(register int i=1;i<=sizev;i++)
for(register int j=1;j<=sizev;j++)
Eij[i][j]=Edges(i,j,0);
for(register int i=1;i<=m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
Eij[u][v].data=Eij[v][u].data=w;
maxi=Max(maxi,w);
}
tot=sizev;
long long ans=0;
for(register int i=1;i<=sizev;i++)
Match[i]=0;
for(register int i=0;i<=sizev;i++)
Dad[i]=i,Leaf[i].clear();
for(register int i=1;i<=sizev;i++)
for(register int j=1;j<=sizev;j++)
Root[i][j]=(i==j?i:0);
for(register int i=1;i<=sizev;i++)
Expect[i]=maxi;
while(Work());
for(register int i=1;i<=sizev;i++)
if(Match[i]&&Match[i]<i)
ans+=Eij[i][Match[i]].data;
printf("%lld\n",ans);
for(register int i=1;i<=sizev;i++)
printf("%d ",Match[i]);
return 0;
}
在
S
e
t
M
a
t
c
h
SetMatch
SetMatch 前
,
,
, 奇环的结构
(
(
(
2
2
2 就是
R
o
o
t
Root
Root
)
)
)
此时的
L
e
a
f
Leaf
Leaf 是
{
1
,
5
,
4
,
3
,
2
}
\{1,5,4,3,2\}
{1,5,4,3,2}
,
,
, 原因可以看
B
l
o
s
s
o
m
B
u
i
l
d
BlossomBuild
BlossomBuild 里的过程
.
.
. 从图中可以看出为什么
p
l
a
c
e
%
2
=
=
1
place\%2==1
place%2==1 时要翻转
.
.
.
最后连接点变成了根
.
.
.
(
(
(
r
o
t
a
t
e
rotate
rotate 的功能
)
)
)
构图技巧
- 当某个图的关系不好辨别时 , , , 可以把它变成它的反图或补图 , , , 从而解决问题 . . .