AT2645 [ARC076D] Exhausted? hall定理+线段树题解
AT2645 [ARC076D] Exhausted?
题目大意:
有$m$个椅子在数轴上排列,第$i$张椅子的坐标为$i$。
高桥君和他的朋友一共有$n$个人。高桥君他们因为玩了太久的游戏,大家的腰和背都很痛,所以他们很有必要坐在椅子上休息一下。
高桥君他们每个人坐的椅子的坐标都很讲究,第 $i$ 个人想坐在坐标在 $l_i$以下(包括$i$的椅子上),或者坐在坐标在$r_i$以上(包括$r_i$的椅子上。当然,一个的椅子只能坐一个人。
可这样计算下去,可能会让他们不能都坐在椅子上休息。青木君关心高桥君他们的健康,尽可能多地增加椅子,让高桥君他们都能够坐在椅子上休息。 椅子可以添加到任意的实数坐标上,请求出需要添加椅子数量的最小值。
简述:
$n$个人,$m$个椅子,每个人能坐在$[1,l_i] \cup [r_i,m]$的椅子。求最少几个人不能坐在椅子上。($1 \leq n,m \leq 200000$)
(这道题可以用贪心来做,详见大佬的博客)
我们可以把这个当成一个二分图,两个点集分别为人和椅子,求$n-最大匹配$。
但是数据范围不允许直接建图,于是我们利用Hall定理。
二分图的Hall定理
Hall定理内容:对于一个二分图$G(X,Y) (|X| \leq |Y|)$,存在完美匹配(匹配个数等于$|X|$)当且仅当对于$X$的任意子集$S$,$S$的邻居个数$|N(S)|$必须大于等于$|S|$。
别问我为什么,因为我也不知道。。。感性理解 百度百科的证明
Hall定理推论:二分图$G(X,Y)$的最大匹配为$|X|-max_{S \subset X}(|S|-|N(S)|)$。
所以可以得出,$ans=n-(n-max(|S|-|N(S)|))=max(|S|-|N(S)|)$.
考虑表示$S$的邻居点集,$N(S)=\bigcup_{i \in S} [1,l_i]\cup[r_i,m]$
这样不能保证连续,不好表示,考虑计算不能坐的椅子的交集,最后取补集即为$N(S)$.
即$|N(S)|=m-|\bigcap_{i \in S} (l_i,r_i)|$.
所以$ans$可以表示为$ans=max(|S|+|\bigcap_{i \in S} (l_i,r_i)|)-m$.
考虑线段树和扫描线求最值,按照$l_i$排序,从小至大边插入边计算。
设当前左右端点为$L,R$(开区间).则当前答案为$max_{r \in (L,R]} (r-L-1+num(r,m))=max_{r \in (L,R]} (r+num(r,m))-L-1$,其中$num(a,b)$代表右端点在$[a,b]$内的已计算的椅子数。
这样就好整多了,节点初值为下标,每处理一个椅子就把$[0,R]$的贡献加一。
代码并不难写。
const int maxn=2e5+5,maxt=maxn<<2;
int tr[maxt],tag[maxt],N,M;
#define ls rt<<1,l,m
#define rs rt<<1|1,m+1,r
#define m ((l+r)>>1)
void pushup(int rt){
tr[rt]=max(tr[rt<<1],tr[rt<<1|1]);
}
void pushtag(int rt,int c){
tag[rt]+=c;
tr[rt]+=c;
}
void pushdown(int rt){
if(tag[rt]){
pushtag(rt<<1,tag[rt]),pushtag(rt<<1|1,tag[rt]);
tag[rt]=0;
}
}
void build(int rt,int l,int r){
if(l==r){
tr[rt]=l;
return;
}
build(ls),build(rs);
pushup(rt);
}
void ch(int rt,int l,int r,int L,int R,int w){
if(r<L||R<l) return;
if(L<=l&&r<=R){
pushtag(rt,w);
return;
}
pushdown(rt);
ch(ls,L,R,w),ch(rs,L,R,w);
pushup(rt);
}
int suan(int rt,int l,int r,int L,int R){
if(r<L||R<l) return 0;
if(L<=l&&r<=R) return tr[rt];
pushdown(rt);
return max(suan(ls,L,R),suan(rs,L,R));
}
struct chr{
int l,r;
friend bool operator<(chr a,chr b){
return a.l==b.l?a.r>b.r:a.l<b.l;
}
}k[maxn];
int MAIN(){
cin>>N>>M;
for(int i=1;i<=N;i++) scanf("%d%d",&k[i].l,&k[i].r);
sort(k+1,k+N+1);
build(1,0,M+1);
int ans=N;
for(int i=1;i<=N;i++){
int l=k[i].l,r=k[i].r;
ch(1,0,M+1,0,r,1);
ans=max(ans,suan(1,0,M+1,l+1,r)-l-1);
}
cout<<max(0,ans-M)<<endl;
return 0;
}