《hall 定理》小记
算是填一下以前的坑吧。
定理
一个关于二分图完美匹配的定理。
设两边的点集为
钦定
定理:
当我们从
证明:
首先如果存在完美匹配,显然对于所有
但是对于充分性的话,我们考虑反证法,设存在这个条件,但没有完美匹配。
而如果对于任意
如果
就这样我们不断从
推论:
一个二分图的最大匹配
上面的式子可以变成
至于如何证明,我们可以用二分图来建立网络流模型,将
那么最小割等于最大流等于最大匹配,得证。
不过我们还可以感性理解,就是说找到一个
来几道例题:
P3488 [POI2009] LYZ-Ice Skates
显然我们可以建立二分图模型跑网络流,但是会爆炸,考虑到题目问我们有没有完美匹配。
可以使用
假设我们选择一个
那么当满足
右边是一个常数我们要是左边尽可能大,维护最大子段和咨客。
时间复杂度
点击查看代码
#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
显然考虑二分答案。
我们设
然后我们显然可以破环成链。
不过对于每次我们都跑一次二分图显然要寄。
我们考虑假设我们选择了连续一段
当满足上面这个条件时,不成立。移一下项有
我们维护前缀最大的
将
时间复杂度
点击查看代码
#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
考虑试填法,然后跑
时间复杂度
点击查看代码
#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
好题。
首先肯定是呀二分答案的。
但是这里我们需要一个很重要的性质,答案在
因为一个人至多用
虽然看上去有一点不太合理,但是他是对的,至于证明我也不会,大概证明分讨一下应该就好了。
反正我们有了这个性质之后就可以做了。
我们要求是否满足有
看到
但是我们如何求
点击查看代码
#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?
首先显然按照一维排序,然后我们从小到大枚举,钦定第
首先加入最右边红色的这个点可以选,那么我们看一下绿色这个点可不可以选,如果选了绿色的点,我们会多五把椅子可以做,但是我们会多
所以我们就要找到一段右边连续的值,使得人减椅子数最大。
这个东西可以用线段树维护,每次加入一个右端点为
然后根据我们
不过有一个要注意的点,就是说当
时间复杂度
点击查看代码
#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
*/
总结:
对于一些题如果要判断能否构成最大匹配,如果直接跑二分图会超时,可以考虑能否用
然后往往需要把式子列出来转换一下要求的东西,因为要求对于所有子集这个条件太苛刻了。通常可以用数据结构或者贪心之类的维护。
本文作者:daduoli
本文链接:https://www.cnblogs.com/ddl1no2home/p/17667245.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步