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(" "); } 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 ",cta,ctb); if (fa[i]) ta[++tpa]=val; } sort(ta+1,ta+tpa+1); // F(i,1,tpa) printf("%lld ",ta[i]); printf(" "); 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(" "); } 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 ",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(iepsilon[1,t] d[i])-max(iepsilon[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(" "); // for (int i=1;i<=m;++i) printf("%d ",k[i]); printf(" "); 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(" "); 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(" "); 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 ",l[rk[i]],r[rk[i]]); printf(" "); // for (int i=1;i<=m;++i) printf("%d ",rk[i]); printf(" "); 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(" "); 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(" "); // for (int i=1;i<=m;++i) printf("%d ",d[i]); printf(" "); build(1,1,m); for (int i=1;i<=m;++i){ // printf("Option s %d ",i); int t=pos[i]; // printf("T = %d ",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 ",C,D,D-C,x); x=min(x,k[i]); printf("%d ",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(" "); // for (int j=1;j<=m;++j) printf("%d ",query(1,1,1,m,j,j,0)); printf(" "); } }
【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 ",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 ",0,v[i][j]); modify(1,0,m+1,0,v[i][j],1); } // printf("Mx on %d %d = %d ",i+1,m+1,query(1,0,m+1,i+1,m+1)); // printf("%d %d ",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 ",ans); }