Hall 定理 是匈牙利算法的基础

大意是说,对于一个二分图

左边的集合记为X,右边的集合记为Y

存在完美匹配,(即匹配数目=min(|X|,|Y|))的充分必要条件是

对于任意一个X的子集,设大小为k,那么和这个子集相连的Y必须不小于k个

这里有一个十分直观的证明

http://blog.csdn.net/werkeytom_ftd/article/details/65658944

【No Name Problem】

给出左右两边20个点的二分图以及一些边,每个点有点权

两边选出点集,要求可以选出一些边,使得子图的每一个点能且仅能被一条边覆盖

并且点权值和大于等于给定的值

【Solution】

我们可以对两边分别进行Hall定理统计,然后扫描一遍即可。

考虑加入左边的X集合以及任意一条匹配边,然后考虑加入Y集合

每次加入一个点,可能出现几种情况

1、已经被覆盖

2、加入匹配边,与之对应的不是X集合中点

3、匹配边对应的是X集合中的点,那么我们不断向前寻找,可知路径上每一个Y集合的点都是由于原因1造成的,所以我们可以调整成为2方案

这样就成了充分必要条件了。

然后扫描统计即可

#include <map>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define F(i,j,k) for (int i=j;i<=k;++i)
#define D(i,j,k) for (int i=j;i>=k;--i)
#define ll long long
#define mp make_pair
int fa[1<<21],fb[1<<21],n,m;
ll ta[1<<21],tb[1<<21];
int tpa,tpb;
int ca[21],cb[21],va[21],vb[21],t;
char s[51]; void print(int x)
{
F(i,0,n-1) printf("%d",(x>>i)&1);
printf("\n");
} void solve()
{
F(i,0,(1<<n)-1)
{
// printf("now is "); print(i);
ll val=0; fa[i]=1; int cta=0,ctb=0;
F(j,0,n-1) if (i&(1<<j))
{
// printf("& with %d ",fa[i^(1<<j)]); print(i^(1<<j));
fa[i]&=fa[i^(1<<j)],cta++,val+=va[j];
}
for (int j=0;j<m;++j) if (cb[j]&i) ++ctb;
if (ctb<cta) fa[i]=0;
// printf("ca %d cb %d\n",cta,ctb);
if (fa[i]) ta[++tpa]=val;
}
sort(ta+1,ta+tpa+1);
// F(i,1,tpa) printf("%lld ",ta[i]); printf("\n");
F(i,0,(1<<m)-1)
{
ll val=0; fb[i]=1; int cta=0,ctb=0;
F(j,0,m-1) if (i&(1<<j))
fb[i]&=fb[i^(1<<j)],cta++,val+=vb[j];
for (int j=0;j<n;++j) if (ca[j]&i) ++ctb;
if (ctb<cta) fb[i]=0;
if (fb[i]) tb[++tpb]=val;
}
sort(tb+1,tb+tpb+1);
// F(i,1,tpb) printf("%lld ",tb[i]); printf("\n");
} void cal()
{
int p=tpb; ll ans=0;
F(i,1,tpa)
{
while (ta[i]+tb[p]>=t&&p>=1) p--;
ans+=tpb-p;
}
printf("%lld\n",ans);
} void Finout()
{
freopen("guard.in","r",stdin);
freopen("guard.out","w",stdout);
} int main()
{
Finout();
scanf("%d%d",&n,&m);
F(i,0,n-1)
{
scanf("%s",s);
F(j,0,m-1)
if (s[j]=='1') ca[i]|=1<<j,cb[j]|=1<<i;
}
// F(i,0,n-1) printf("peoa %d ",i),print(ca[i]);
// F(i,0,m-1) printf("peob %d ",i),print(cb[i]);
F(i,0,n-1) scanf("%d",&va[i]);
F(i,0,m-1) scanf("%d",&vb[i]);
solve();
scanf("%d",&t);
cal();
return 0;
}

  

【BZOJ 2138】

给定不包含的一些区间,每堆有一些石子,问每次从选定的区间内取K[i]个,在满足前面最大的情况下,当前最多能取出多少石子

【Solution】

考虑Hall定理,按照区间左端点排序,判定时只需要判定一个区间(删除永远不会用到的区间)

所以我们可以得到

$\sum _{i=l}^{r} b_i <= \sum _{i=L[l]}^{R[r]} a_i$

然后用前缀和变换一下

$c[i]=sb[i]-sa[R[i]]$
$d[i]=sb[l-1]-sa[L[l]-1]$

然后要求$c[r]<=d[l]$

然后我们考虑操作对上述条件的影响。

我们可以

令$x=min(i\epsilon[1,t] d[i])-max(i\epsilon[t,m]c[i])$

然后令$b[i]=x$

然后再线段树上维护即可

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005 int a[maxn],l[maxn],r[maxn],k[maxn],op[maxn],ban[maxn],dim[maxn];
int sa[maxn],c[maxn],d[maxn],rk[maxn],pos[maxn];
int data[2][maxn<<2],mx[2][maxn<<2],mn[2][maxn<<2],tag[2][maxn<<2]; long long sqt(int x)
{
return x*x;
} bool cmp(int a,int b)
{return l[a]==l[b]?r[a]<r[b]:l[a]<l[b];} void make_tag(int id,int o,int f)
{
tag[id][o]+=f;
mx[id][o]+=f;
mn[id][o]+=f;
} void pushdown(int o)
{
for (int i=0;i<2;++i)
if (tag[i][o]!=0){
make_tag(i,o<<1,tag[i][o]);
make_tag(i,o<<1|1,tag[i][o]);
tag[i][o]=0;
}
} void update(int o)
{
for (int i=0;i<2;++i)
mx[i][o]=max(mx[i][o<<1],mx[i][o<<1|1]),
mn[i][o]=min(mn[i][o<<1],mn[i][o<<1|1]);
} void build(int o,int l,int r)
{
if (l==r){
data[0][o]=mx[0][o]=mn[0][o]=c[l];
data[1][o]=mx[1][o]=mn[1][o]=d[l];
tag[0][o]=tag[1][o]=0;
return ;
}
int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
update(o);
} void modify(int id,int o,int l,int r,int L,int R,int f)
{
if (L>R) return ;
if (L<=l&&r<=R){
make_tag(id,o,f);
return ;
}
int mid=(l+r)>>1; pushdown(o);
if (R>mid) modify(id,o<<1|1,mid+1,r,L,R,f);
if (L<=mid) modify(id,o<<1,l,mid,L,R,f);
update(o);
} int query(int id,int o,int l,int r,int L,int R,int f)
{
if (L<=l&&r<=R){
if (f) return mx[id][o];
else return mn[id][o];
}
int mid=(l+r)>>1; pushdown(o);
if (R<=mid) return query(id,o<<1,l,mid,L,R,f);
if (L>mid) return query(id,o<<1|1,mid+1,r,L,R,f);
if (f) return max(query(id,o<<1,l,mid,L,R,f),query(id,o<<1|1,mid+1,r,L,R,f));
else return min(query(id,o<<1,l,mid,L,R,f),query(id,o<<1|1,mid+1,r,L,R,f));
} int main()
{ #ifdef WXL
freopen("in.txt","r",stdin);
freopen("wa.txt","w",stdout);
#endif int n,x,y,z,p,m;
scanf("%d%d%d%d%d",&n,&x,&y,&z,&p);
for (int i=1;i<=n;++i) a[i]=(sqt(i-x)+sqt(i-y)+sqt(i-z))%p;
scanf("%d",&m); if (m==0) return 0;
scanf("%d%d%d%d%d%d",&k[1],&k[2],&x,&y,&z,&p);
for (int i=1;i<=m;++i) scanf("%d%d",&l[i],&r[i]);
for (int i=3;i<=m;++i) k[i]=(x*k[i-1]+y*k[i-2]+z)%p; // for (int i=1;i<=n;++i) printf("%d ",a[i]); printf("\n");
// for (int i=1;i<=m;++i) printf("%d ",k[i]); printf("\n"); for (int i=1;i<=m;++i) op[l[i]]++,op[r[i]+1]--;
for (int i=1;i<=n;++i) op[i]+=op[i-1];
for (int i=1;i<=n;++i){
if (!op[i]) ban[i]+=1;
dim[i]=dim[i-1]+ban[i];
}
// for (int i=1;i<=n;++i) printf("%d ",dim[i]); printf("\n");
for (int i=1;i<=m;++i) l[i]-=dim[l[i]],r[i]-=dim[r[i]];
for (int i=1;i<=n;++i) if (!ban[i]) a[i-dim[i]]=a[i];
n-=dim[n]; // for (int i=1;i<=n;++i) printf("%d ",a[i]); printf("\n");
for (int i=1;i<=m;++i) rk[i]=i;
sort(rk+1,rk+m+1,cmp);
for (int i=1;i<=m;++i) pos[rk[i]]=i;
// for (int i=1;i<=m;++i) printf("%d %d\n",l[rk[i]],r[rk[i]]); printf("\n");
// for (int i=1;i<=m;++i) printf("%d ",rk[i]); printf("\n");
for (int i=1;i<=n;++i) sa[i]=a[i]+sa[i-1];
// for (int i=1;i<=n;++i) printf("%d ",sa[i]); printf("\n");
for (int i=1;i<=m;++i){
c[i]=-sa[r[rk[i]]];
d[i]=-sa[l[rk[i]]-1];
} // for (int i=1;i<=m;++i) printf("%d ",c[i]); printf("\n");
// for (int i=1;i<=m;++i) printf("%d ",d[i]); printf("\n"); build(1,1,m); for (int i=1;i<=m;++i){
// printf("Option s %d\n",i);
int t=pos[i];
// printf("T = %d\n",t);
int D=query(1,1,1,m,1,t,0),C=query(0,1,1,m,t,m,1);
int x=D-C;
// printf("%d %d %d %d\n",C,D,D-C,x);
x=min(x,k[i]);
printf("%d\n",x);
modify(0,1,1,m,t,m,x);
modify(1,1,1,m,t+1,m,x);
// for (int j=1;j<=m;++j) printf("%d ",query(0,1,1,m,j,j,0)); printf("\n");
// for (int j=1;j<=m;++j) printf("%d ",query(1,1,1,m,j,j,0)); printf("\n");
} }

 

【ARC 076 F】

给定每个人的区间要求$<=l_i$ 或者$>=r_i$

求最大匹配,$n<=10w$

【Solution】

考虑Hall定理,我们只需要考虑整个区间或者一首一尾两种情况

整个区间比较容易计算最大不能匹配数目,略去

对于一首一尾的情况即统计$l_i<=s$ && $r_i>=t$的数目

然后对于每一个人看作$(l_i,r_i)$这个点

然后用扫描线解决即可

#include <bits/stdc++.h>
using namespace std; #define maxn 200050
int tag[maxn<<2],mx[maxn<<2]; void update(int o)
{
mx[o]=max(mx[o<<1],mx[o<<1|1]);
} void make_tag(int o,int f)
{
tag[o]+=f;
mx[o]+=f;
} void pushdown(int o)
{
if (tag[o]!=0){
make_tag(o<<1,tag[o]);
make_tag(o<<1|1,tag[o]);
tag[o]=0;
}
} void modify(int o,int l,int r,int L,int R,int f)
{
// printf("Modiy %d %d %d %d %d %d\n",o,l,r,L,R,f);
if (L<=l&&r<=R){
make_tag(o,f);
return ;
}
int mid=(l+r)>>1; pushdown(o);
if (R>mid) modify(o<<1|1,mid+1,r,L,R,f);
if (L<=mid) modify(o<<1,l,mid,L,R,f);
update(o);
} void build(int o,int l,int r)
{
if (l==r){
mx[o]=l;
tag[o]=0;
return;
}
int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
update(o);
} int query(int o,int l,int r,int L,int R)
{
if (L<=l&&r<=R) return mx[o];
int mid=(l+r)>>1; pushdown(o);
if (R<=mid) return query(o<<1,l,mid,L,R);
else if (L>mid) return query(o<<1|1,mid+1,r,L,R);
else return max(query(o<<1,l,mid,L,R),query(o<<1|1,mid+1,r,L,R));
} vector <int> v[maxn]; int main()
{ #ifdef WXL
freopen("in.txt","r",stdin);
#endif int n,m,ans;
scanf("%d%d",&n,&m); ans=max(0,n-m);
for (int i=1;i<=n;++i){
int x,y;
scanf("%d%d",&x,&y);
v[x].push_back(y);
}
build(1,0,m+1);
for (int i=0;i<=m;++i){
for (int j=0;j<(int)v[i].size();++j){
// printf("%d -- %d+1\n",0,v[i][j]);
modify(1,0,m+1,0,v[i][j],1);
}
// printf("Mx on %d %d = %d\n",i+1,m+1,query(1,0,m+1,i+1,m+1));
// printf("%d %d\n",i,query(1,0,m+1,i+1,m+1));
ans=max(ans,query(1,0,m+1,i+1,m+1)-i-m-1);
}
printf("%d\n",ans); }

  

Hall 定理的更多相关文章

  1. Hall定理 二分图完美匹配

    充分性证明就先咕了,因为楼主太弱了,有一部分没看懂 霍尔定理内容 二分图G中的两部分顶点组成的集合分别为X, Y(假设有\(\lvert X \rvert \leq \lvert Y \rvert\) ...

  2. 【CF981F】Round Marriage(二分答案,二分图匹配,Hall定理)

    [CF981F]Round Marriage(二分答案,二分图匹配,Hall定理) 题面 CF 洛谷 题解 很明显需要二分. 二分之后考虑如果判定是否存在完备匹配,考虑\(Hall\)定理. 那么如果 ...

  3. bzoj3693: 圆桌会议 二分图 hall定理

    目录 题目链接 题解 代码 题目链接 bzoj3693: 圆桌会议 题解 对与每个人构建二分,问题化为时候有一个匹配取了所有的人 Hall定理--对于任意的二分图G,G的两个部分为X={x1,x2,- ...

  4. TCO 2015 1A Hard.Revmatching(Hall定理)

    \(Description\) 给定一个\(n\)个点的二分图,每条边有边权.求一个边权最小的边集,使得删除该边集后不存在完备匹配. \(n\leq20\). \(Solution\) 设点集为\(S ...

  5. BZOJ.3693.圆桌会议(Hall定理 线段树)

    题目链接 先考虑链.题目相当于求是否存在完备匹配.那么由Hall定理,对于任意一个区间[L,R],都要满足[li,ri]完全在[L,R]中的ai之和sum小于等于总位置数,即R-L+1.(其实用不到H ...

  6. BZOJ.5404.party(树链剖分 bitset Hall定理)

    题目链接 只有指向父节点的单向道路,所以c个人肯定在LCA处汇合.那么就成了有c条到LCA的路径,求最大的x,满足能从c条路径中各选出x个数,且它们不同. 先要维护一条路径的数的种类数,可以树剖+每条 ...

  7. LOJ.6062.[2017山东一轮集训]Pair(Hall定理 线段树)

    题目链接 首先Bi之间的大小关系没用,先对它排序,假设从小到大排 那么每个Ai所能匹配的Bi就是一个B[]的后缀 把一个B[]后缀的匹配看做一条边的覆盖,设Xi为Bi被覆盖的次数 容易想到 对于每个i ...

  8. loj#6062. 「2017 山东一轮集训 Day2」Pair hall定理+线段树

    题意:给出一个长度为 n的数列 a和一个长度为 m 的数列 b,求 a有多少个长度为 m的连续子数列能与 b匹配.两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对,两个数可以配对当 ...

  9. 【BZOJ2138】stone Hall定理+线段树

    [BZOJ2138]stone Description 话说Nan在海边等人,预计还要等上M分钟.为了打发时间,他玩起了石子.Nan搬来了N堆石子,编号为1到N,每堆包含Ai颗石子.每1分钟,Nan会 ...

  10. BZOJ1135:[POI2009]Lyz(线段树,Hall定理)

    Description 初始时滑冰俱乐部有1到n号的溜冰鞋各k双.已知x号脚的人可以穿x到x+d的溜冰鞋. 有m次操作,每次包含两个数ri,xi代表来了xi个ri号脚的人.xi为负,则代表走了这么多人 ...

随机推荐

  1. EM理解(转)

    EM是我一直想深入学习的算法之一,第一次听说是在NLP课中的HMM那一节,为了解决HMM的参数估计问题,使用了EM算法.在之后的MT中的词对齐中也用到了.在Mitchell的书中也提到EM可以用于贝叶 ...

  2. Activiti学习记录(五)

    1.排他网关 说明: 1) 一个排他网关对应一个以上的顺序流 2) 由排他网关流出的顺序流都有个conditionExpression元素,在内部维护返回boolean类型的决策结果. 3) 决策网关 ...

  3. Java读取各种文件格式内容

    所需的jar包哦也不要太记得了,大家可以搜搜,直接上代码: import java.io.BufferedInputStream; import java.io.File; import java.i ...

  4. linux运维、架构之路-MySQL多实例

    一.MySQL多实例介绍            一台服务器上开启多个不同的服务端口(3306,3307,3308),运行多个MySQL服务进程,共用一套MySQL安装程序,多实例MySQL在逻辑上看是 ...

  5. mysql安装记录

    一.创建mysql用户 useradd mysql 二.解压 tar -zxvf mysql-5.6.38.tar.gz 三.安装依赖包 yum install -y ncurses-devel li ...

  6. static关键字 详解

    原文地址:http://blog.csdn.net/keyeagle/article/details/6708077 google了近三页的关于C语言中static的内容,发现可用的信息很少,要么长篇 ...

  7. PowerShell批量启动/关闭Azure VM

    备注:以下例子中出现的JohnsonWeb, JohnsonVm均是虚拟机的名称.在运行Powershell脚本之前,请导入您的订阅文件. 根据条件启动/关闭虚拟机,例如根据虚拟机名称,批量启动/关闭 ...

  8. Python 代码优化技巧(一)

    Table of Contents 1. 代码优化Part1 1.1. if 判断的短路特性 1.2. join 合并字符串 1.3. while 1 和 while True 1.4. cProfi ...

  9. TCP的运输连接管理

    TCP的运输连接管理 TCP是面向连接的协议,有三个阶段:连接建立.数据传送 和 连接释放.运输连接的管理就是使运输连接的简历和释放都能正常地进行. 在TCP连接建立过程中要解决一下三个问题: 1.  ...

  10. 2 - JVM随笔分类(JVM堆的内存回收)

    JVM常用的回收算法是: 标记/清除算法 标记/复制算法 标记/整理算法 其中上诉三种算法都先具备,标记阶段,通过标记阶段,得到当前存活的对象,然后再将非标记的对象进行清除,而对象内存中对象的标记过程 ...