【洛谷3679】[CERC2016] Bipartite Blanket(霍尔定理)

点此看题面

  • 给定一张两边点数分别为(n,m)的二分图,每个点有一个权值。
  • 求权值和不小于(t)且存在一组匹配将其完全覆盖的点集个数。
  • (n,mle20)

左右分离

暂且不管权值和不小于(t)的问题,而是考虑如何判断是否存在一组匹配将一个点集完全覆盖。

显然,它的一个必要条件是点集中左半边的点与原图中右半边的点存在完美匹配,点集中右半边的点与原图中左半边的点存在完美匹配。

而这个条件同时也是充分的,因为只要这两个完美匹配都存在,我们必然可以经过调整构造出一组合法方案来。

这样一来就可以将左右分离开来,各自讨论了。

霍尔定理

要判定是否存在完美匹配,霍尔定理是一个非常好用的工具。

它断言,一个点集(S)存在完美匹配,当且仅当任意(Tsubseteq S),存在不少于(|T|)个与该子集中点能够匹配的点。

这其实也就等价于(S)的所有大小为(|S|-1)的子集都存在完美匹配。

单侧暴枚子集+两侧双指针合并

在这题中,对于两侧,我们分别暴力枚举每个子集,然后考虑用霍尔定理来判定。

先判断这个子集的匹配点个数是否不小于它的大小,然后再枚举子集中的所有元素,判断将其删去后是否仍能得到完美匹配即可。

然后我们分别存下这两部分合法点集的点权和,排序之后,只要双指针扫一遍,就能求出两边合起来有多少权值和不小于(t)的合法点集了。

代码:(O(n2^n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
using namespace std;
int n,m,a[N+5],b[N+5];char w[N+5][N+5];vector<int> s1,s2;
int f[1<<N],g[1<<N],h[1<<N],c[1<<N];I void Solve(vector<int>& s)
{
	RI i,j,l=1<<n;for(i=1;i<=n;++i) for(g[1<<i-1]=0,h[1<<i-1]=a[i],j=1;j<=m;++j) w[i][j]&1&&(g[1<<i-1]|=1<<j-1);//对每个点记录点权以及能匹配的点集
	for(i=0;i^l;++i) c[i]=c[i>>1]+(i&1),c[i]>1&&(g[i]=g[i^(i&-i)]|g[i&-i],h[i]=h[i^(i&-i)]+h[i&-i]);//对每个子集求出权值和以及能匹配的点集
	for(f[0]=i=1;i^l;++i) for(f[i]=c[g[i]]>=c[i],j=0;j^m;++j) i>>j&1&&!f[i^(1<<j)]&&(f[i]=0);//暴枚每个子集,利用霍尔定理判定是否存在完美匹配
	for(i=0;i^l;++i) f[i]&&(s.push_back(h[i]),0);sort(s.begin(),s.end());//存下所有合法点集的点权和,排序
}
int main()
{
	RI i,j;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",w[i]+1);
	for(i=1;i<=n;++i) scanf("%d",a+i);for(i=1;i<=m;++i) scanf("%d",b+i);RI t;scanf("%d",&t);
	Solve(s1);for(i=1;i<=m;++i) for(j=i+1;j<=n;++j) swap(w[i][j],w[j][i]);//求解一侧;交换邻接矩阵
	for(i=1;i<=max(n,m);++i) swap(a[i],b[i]);swap(n,m),Solve(s2);//交换点权,交换点数,求解另一侧
	RI z;long long ans=0;for(i=s1.size()-1,j=0,z=s2.size();~i&&j^z;ans+=z-j,--i) W(j^z&&s1[i]+s2[j]<t) ++j;//双指针
	return printf("%lld
",ans),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3679.html