华风夏韵,洛水天依!|

daduoli

园龄:1年10个月粉丝:12关注:43

《hall 定理》小记

算是填一下以前的坑吧。

Hall 定理

一个关于二分图完美匹配的定理。

设两边的点集为 |X|,|Y| ,当匹配数达到 min(|X|,|Y|) 的时候,我们称之为有完美匹配。

钦定 |X||Y|

定理:

当我们从 |X| 集合中任意选择一个点集 |S| ,然后把点集 |S| 连着的 |Y| 中的点,记为 N(S) ,也就是邻域。当满足对于任意 S ,都有 |S|N(S) 那么这个图就存在完美匹配。

证明:

首先如果存在完美匹配,显然对于所有 S ,都有 |S|N(S)

但是对于充分性的话,我们考虑反证法,设存在这个条件,但没有完美匹配。

而如果对于任意 S 都满足 SN(S) ,那么也就是说此时,如果 S 中有一个点 a 没有匹配到,那么他可以选择一个与他连接的点进行匹配,记为 b

如果 b 没被匹配过,那就只就匹配。如果已经被匹配过了,那么就把与 b 匹配的点加入 |S| 中,加入后,我们能保证右侧至少有一个点是我们之前没有尝试选择过的,否则就不满足我们的前提条件。让这个与 b 匹配的点去匹配另一个与他相连的点。

就这样我们不断从 |X| 中加入新的点进 |S| 但是因为 |X| 是有限的,所以最终一定能够匹配成功,所以命题矛盾,原命题成立。

推论:

一个二分图的最大匹配 =|X|max(|S||N(S)|)

上面的式子可以变成 min(|X||S|+|N(S)|)

至于如何证明,我们可以用二分图来建立网络流模型,将 |X||S| 看成左边割掉的点,将 |N(S)| 看做右边割掉的点,那么取一个 min 就是最小割。

那么最小割等于最大流等于最大匹配,得证。

不过我们还可以感性理解,就是说找到一个 |S|N(S) 差值最大的集合,减去显然就是我们最多能匹配到的点数。

来几道例题:

P3488 [POI2009] LYZ-Ice Skates

显然我们可以建立二分图模型跑网络流,但是会爆炸,考虑到题目问我们有没有完美匹配。

可以使用 hall 定理快速判断。

假设我们选择一个 lr 中的人,作为我们的 |S|

那么当满足

i=lrnumi>(r+dl+1)×k=i=lrnumi>(rl+1)×k+d×k=i=lR(numik)>d×k

右边是一个常数我们要是左边尽可能大,维护最大子段和咨客。

时间复杂度 O(nlogm)

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=5e5+10;
LL n,m,k,d;
struct daduoli {
	LL s,ls,rs,d;
}tr[MAXN*4];
void psup(int u) {
	int l=(u<<1),r=(u<<1|1);
	tr[u].s=tr[l].s+tr[r].s;
	tr[u].ls=max(tr[l].ls,tr[l].s+tr[r].ls);
	tr[u].rs=max(tr[r].rs,tr[r].s+tr[l].rs);
	tr[u].d=max(max(tr[l].d,tr[r].d),tr[l].rs+tr[r].ls);
}
void update(int u,int l,int r,int x,LL y) {
	if(l>x||r<x) return ;
	if(l==r) {
		tr[u].s+=y;
		tr[u].ls+=y;
		tr[u].rs+=y;
		tr[u].d+=y;
		return ;
	}
	int mid=(l+r)/2;
	update((u<<1),l,mid,x,y);
	update((u<<1|1),mid+1,r,x,y);
	psup(u);
}
int main () {
	scanf("%lld%lld%lld%lld",&n,&m,&k,&d);
	for(int i=1;i<=n;++i) {
		update(1,1,n,i,-k);
	}
	for(int i=1;i<=m;++i) {
		LL x,y;
		scanf("%lld%lld",&x,&y);
		update(1,1,n,x,y);
		if(tr[1].d>(LL)k*d) puts("NIE");
		else puts("TAK");
	}
	return 0;
}

Round Marriage

显然考虑二分答案。

我们设 nli 表示 i 能配对的最左边的新娘是谁(可以走过环), nri 表示 i 能配对的最右边的新娘是谁。

然后我们显然可以破环成链。

不过对于每次我们都跑一次二分图显然要寄。

我们考虑假设我们选择了连续一段 lr 的新郎,因为肯定是连续一段的新郎才能使得能配对的新娘尽可能的少。

rl+1>nrrnrl+1

当满足上面这个条件时,不成立。移一下项有

nlll>nrrr

我们维护前缀最大的 nlll 即可。

a,b 都复制四份貌似很好操作。

时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=2e5+10;
LL n,L;
LL a[MAXN*4],b[MAXN*4],nl[MAXN*4],nr[MAXN*4];
bool check(LL k) {
	int l=1,r=0;
	for(int i=1;i<=n*3;++i) {
		while(r<n*4&&a[i]+k>=b[r+1]) ++r;
		while(l<=r&&a[i]-b[l]>k) ++l;
		nl[i]=l; nr[i]=r;
	}
	LL da=-1e18;
	for(int i=n+1;i<=n*3;++i) {
		da=max(da,nl[i]-i);
		if(nr[i]-i<da) return false;
	}
	return true;
}
LL erfind() {
	int l=-1,r=L+1,mid;
	while(l+1<r) {
		mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid;
	}
	return r;
}
int main () {
	scanf("%lld%lld",&n,&L);
	for(int i=1;i<=n;++i) {
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=n;++i) {
		scanf("%lld",&b[i]);
	}
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	for(int i=1;i<=n*3;++i) a[i+n]=a[i]+L;
	for(int i=1;i<=n*3;++i) b[i+n]=b[i]+L;
	printf("%lld\n",erfind());
	return 0;
}

Allowed Letters

考虑试填法,然后跑 hall 定理即可。

时间复杂度 O(n2) 貌似有一个 2 的常数。

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=2e5+10;
char ch[MAXN],ch1[MAXN],ans[MAXN];
int n,m,num[MAXN],lim[(1<<6)],p[6];
void add(int i,int val) {
	for(int j=1;j<(1<<6);++j) {
		if((num[i]&j)==num[i]) {
			lim[j]+=val;
		}
	}
}
bool check() {
	for(int j=0;j<(1<<6);++j) {
		int ans=0;
		for(int q=0;q<6;++q) {
			if(!((j>>q)&1)) continue;
			ans+=p[q];
		}
		if(ans<lim[j]) return false;
	}
	return true;
}
int main () {
	scanf("%s",ch+1);
	n=strlen(ch+1);
	for(int i=1;i<=n;++i) {
		++p[ch[i]-'a'];
	}
	scanf("%d",&m);
	for(int i=1;i<=m;++i) {
		int opt;
		scanf("%d",&opt);
		scanf("%s",ch1+1);
		int ls=strlen(ch1+1);
		for(int j=1;j<=ls;++j) {
			num[opt]|=(1<<(ch1[j]-'a'));
		}
	}
	for(int i=1;i<=n;++i) {
		if(!num[i]) num[i]=(1<<6)-1;
		add(i,1);
	}
	for(int i=1;i<=n;++i) {
		bool sf=0;
		for(int j=0;j<6;++j) {
			if(!num[i]||(num[i]&(1<<j))==(1<<j)) {
				if(!p[j]) continue;
				--p[j]; add(i,-1);
				if(check()) {
					sf=1;
					ans[i]=char(j+'a');
					break;
				}
				++p[j]; add(i,1);
			}
		}
		if(!sf) {
			puts("Impossible");
			return 0;
		}
	}
	for(int i=1;i<=n;++i) cout<<ans[i];
	puts("");
	return 0;
}

[ARC106E] Medals

好题。

首先肯定是呀二分答案的。

但是这里我们需要一个很重要的性质,答案在 2kn 以内。

因为一个人至多用 2k 那么 n 个人至多用 2kn

虽然看上去有一点不太合理,但是他是对的,至于证明我也不会,大概证明分讨一下应该就好了。

反正我们有了这个性质之后就可以做了。

我们要求是否满足有 |S|>|N(S)|

看到 n 很小,显然可以考虑状压来求。

但是我们如何求 |N(S)| 呢,显然如果要求 (|N(S)|) 他的条件是 | 不好处理,考虑转换成 & ,我们可以用 mid 减去不在邻域里面的数就可以算出,而怎么算这玩意呢,状压枚举子集要 O(3n) 显然不行,可以使用高位前缀和,虽然还不会,不过抄就完了,时间复杂度 O(2kn+2nnlog(2kn))

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=20,NN=3600010;
LL n,m,k;
LL a[MAXN],st[NN],f[(1<<18)],S;
bool check(LL mid) {
	for(int i=0;i<S;++i) f[i]=0;
	for(int i=1;i<=mid;++i) ++f[st[i]];
	for(int i=0;i<n;++i) {
		for(int j=0;j<S;++j) {
			if(!((j>>i)&1)) f[j|(1<<i)]+=f[j];
		}
	}
	for(int i=0;i<S;++i) {
		int cnt=0;
		for(int j=0;j<n;++j) {
			if((i>>j)&1) ++cnt;
		}
		if(mid-f[S-1-i]<cnt*k) return false;
	}
	return true;
}
LL erfind() {
	LL l=0,r=(LL)m+1,mid;
	while(l+1<r) {
		mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid;
	}
	return r;
}
int main () {
	scanf("%lld%lld",&n,&k); m=2*n*k; S=(1<<n);
	for(int i=1;i<=n;++i) {
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=m;++i) {
		for(int j=1;j<=n;++j) {
			int t=(i-1)%(a[j]*2)+1;
			if(t<=a[j]) st[i]|=(1<<(j-1));
		}
	}
	printf("%lld\n",erfind());
	return 0;
}

[ARC076F] Exhausted?

首先显然按照一维排序,然后我们从小到大枚举,钦定第 i 个必选,这时候考虑右端点的影响。

image

首先加入最右边红色的这个点可以选,那么我们看一下绿色这个点可不可以选,如果选了绿色的点,我们会多五把椅子可以做,但是我们会多 10 个人,这显然是赚的可以选,而对于黄色的点,如果选了,多三个人,但是会多 5 把椅子显然是亏的。

所以我们就要找到一段右边连续的值,使得人减椅子数最大。

这个东西可以用线段树维护,每次加入一个右端点为 ri 的人,就把 0ri 都加一,表示如果选了他们这之中的一个数,人与椅子的差值加一,所以其实线段树维护的就是一个后缀人减椅子数。

然后根据我们 hall 定理的推论,可以知道最大匹配数 =|X|max(|S||N(S)|) 然后根据这个算一下,就可以知道要加多少把椅子了。

不过有一个要注意的点,就是说当 l,r 重叠的时候,我们是视作减去他们之中重叠的椅子,最后再减去,具体看代码实现。

时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
typedef long long LL; 

using namespace std;
const int MAXN=2e5+10;
int n,m; 
struct daduoli {
	int l,r;
}a[MAXN];
int tr[MAXN*4],lb[MAXN*4];
void psup(int node) {
	tr[node]=max(tr[(node<<1)],tr[(node<<1|1)]);
}
void build_tree(int node,int l,int r) {
	if(l==r) {
		if(l!=0) tr[node]=-(m-l+1);
		else tr[node]=-m;
		return ;
	}
	int mid=(l+r)/2;
	build_tree((node<<1),l,mid);
	build_tree((node<<1|1),mid+1,r);
	psup(node);
}
bool cmp(daduoli a,daduoli b) {
	if(a.l!=b.l) return a.l<b.l;
	return a.r>b.r;
}
void zx(int node,int x) {
	lb[node]+=x;
	tr[node]+=x;
}
void psdn(int node) {
	if(lb[node]) {
		int ls=(node<<1),rs=(node<<1|1);
		zx(ls,lb[node]);
		zx(rs,lb[node]);
		lb[node]=0;
	}
}
void update(int node,int l,int r,int x,int y) {
	if(l>y||r<x) return ;
	if(l>=x&&r<=y) {
//		if(l==0) cout<<tr[node]<<' ';
		zx(node,1);
//		if(l==0) cout<<tr[node]<<" ";
		return ;
	}
	int mid=(l+r)/2;
	psdn(node);
	update((node<<1),l,mid,x,y);
	update((node<<1|1),mid+1,r,x,y);
	psup(node);
}
int main () {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%d%d",&a[i].l,&a[i].r);
	}
	build_tree(1,0,m+1);
	sort(a+1,a+1+n,cmp);
	int ans=n;
	int l=1;
	for(int i=1;i<=n;++i) {
		for( ; l<=a[i].l;++l) {
			update(1,0,m+1,0,l);
		}
		update(1,0,m+1,0,a[i].r);
		ans=min(ans,n-max(0,(tr[1]-a[i].l)));
	}
	cout<<n-ans;
	return 0;
}
/*
10 7
1 8
1 8
1 8
1 8
1 8
1 8 
1 8
1 8
1 8
1 8
*/

总结:

对于一些题如果要判断能否构成最大匹配,如果直接跑二分图会超时,可以考虑能否用 hall 定理判定合法性。

然后往往需要把式子列出来转换一下要求的东西,因为要求对于所有子集这个条件太苛刻了。通常可以用数据结构或者贪心之类的维护。

本文作者:daduoli

本文链接:https://www.cnblogs.com/ddl1no2home/p/17667245.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   daduoli  阅读(82)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起