匹配专题

二分图的匹配

最大匹配

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 中的所有边 . . .

证明 : : :

  1. 最小点覆盖大于等于最大匹配

假设u和v是最大匹配中 , , , 某一条匹配边的两个端点 , , , 则u和v不可能同时与非匹配点有边相连 , , , 否则最大匹配数目至少要增加 1 1 1 , , , 矛盾 . . . 即u和v最多只有一个点可以与若干条非匹配点相连接 , , , 因此 , , , 只需要选择有连接非匹配点的点进入最小点覆盖集合就可以 . . . 证明完毕 . . .

  1. 最大独立集 = = = 点数 − - 最大匹配

证明 : : :

假设已经得到最大匹配 , , , 在某条匹配边(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;
}

特殊情况 : : :

  1. 通过为右部点添加虚点来保证左部点个数小于等于右部点个数 . . . ( ( ( 是必要的 , , , K m Km Km 需要在完备匹配中保证正确 ) ) )

  2. 然后 , , , 我们要将原本不存在的边连成虚边 . . .

  3. 对于需要特判无解的题目 , , , 将虚边边权设为 − I n f -Inf Inf , , , 如果被迫选了 − I n f -Inf Inf 的边就输出无解 . . . ( ( ( 实现上就是先跑 K m Km Km , , , 然后在统计答案时判断有没有虚边 ) ) )

  4. 对于允许非完美匹配的题目 , , , 将虚边边权设为 0 0 0 即可 . . .

  5. ( ( ( 对于无解 , , , 也可以选择加一个虚点作为退路 , , , 跑一遍 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 BronKerbosch 算法

该算法本质也是 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 的功能 ) ) )

构图技巧

  1. 当某个图的关系不好辨别时 , , , 可以把它变成它的反图或补图 , , , 从而解决问题 . . .
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值