Exhausted? 题解(线段树)
Exhausted? 题解
前言:
看本篇题解,您如果想要掌握所有知识点的话,请您先去了解下什么是霍尔定理,当然如果可以的话,可以去看看我的这个博客。
涉及的算法和思想知识点:
- 线段树、扫描线。
- 霍尔定理。
- 较少的容斥原理。
正文:
理论分析:
-
从简单入手:我们想想,要是值域再小一点的话,我们可以怎么做?我的想法是直接把人和合理的区间各个位置连边,形成一个二分图,求解最大匹配找到最多的能不添加椅子就可以做的人,然后用总共的减去就是最小的了。
-
思维拔升:我们发现不可以建立图时,不然就是算法有问题不然就是要用各种理论来简化,对于二分图而言,一般建不了图,就是霍尔定理。我们将人看做 \(V1\),根据定理,那么我们就是求解 \(\max(|S1|-|S2|)\) 这个柿子。
-
举例化简:现在我们随便选择两个点作为 \(S1\) 那么就是求解他们 \(S2\) 的并。我们稍微容斥一下,变为求解两个点区间的补集的交集的补集。这么说很混乱,我直接举例子吧,就是求 \([L1+1,R1-1]\) 和 \([L2+1,R2-1]\) 的交集的补集。我们用柿子表达,就是求 \(\max(|S1|-m+R-L+1)\)。注意下,要是没有交集,答案就是 \(n-m\),所以答案至少为它。
-
思考解决最值的方向:我们为了让上述柿子最大,像这种题目不然就是贪心,不然就是找函数最值,不然就是硬着头皮想方法维护。贪心的话我实在想不出来,用函数的话,我们会发现我们点选得越多,\(R-L+1\) 却不会变长。两者是“你增加我可能减少”的关系,所以从函数本身来看似乎就不行了。那就只能硬着头皮去维护这个柿子了。
代码实现思路(重点):
因为我觉得维护很难想到,所以专门开了个重点来。
考虑到一个交集的左端点一定是一个原来的左端点之一,右端点同理。于是我现在想找一个左端点确定的交集查找答案(通过枚举线段来确定)。
当这个交集的左端点确定时,我选择的点只能是左端点在它左侧,右端点在他右侧的线段(否则的话交集左端点会变化或者不会有交集)。由于左端点确定,我们假设已经确定这个交集的右端点为 \(K\),于是乎我们就可以得到这个交集 \([L,K]\) 给出的最终答案为 \(|S1|-m+K-L+1\)。我们此时还可以扩展 \(|S1|\) 让答案更优,这些可以添加的点要求其左右端点不会更改交集即可。
所以最后就是
\(K-L-1-m+\sum_j [rj>=k] , [lj<=L]\)。
具体做法是在线段树上把i位置的初值设为i,然后每次将 \([l,r]\) 区间+1,求$ [l,m]$ 的最大值。
代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
int le;
int ri;
}stu[200005];
bool cmp(node x,node y){
if(x.le!=y.le)return x.le<y.le;
else return x.ri<y.ri;
}
int ans=0;
struct nod{
int le;
int ri;
int val;
int lz;
}tree[800005];
vector<int>h[200005];
void pushup(int rt){
tree[rt].val=max(tree[rt*2].val,tree[rt*2+1].val);
}
void pushdown(int rt){
if(!tree[rt].lz){
return;
}
tree[rt*2].lz+=tree[rt].lz;
tree[rt*2+1].lz+=tree[rt].lz;
tree[rt*2].val+=tree[rt].lz;
tree[rt*2+1].val+=tree[rt].lz;
tree[rt].lz=0;
return;
}
void build(int rt,int L,int R){
tree[rt].le=L;
tree[rt].ri=R;
if(L==R){
tree[rt].val=L;
return;
}
int mid=(L+R)>>1;
build(rt*2,L,mid);
build(rt*2+1,mid+1,R);
pushup(rt);
}
void add(int rt,int L,int R,int v){
int le=tree[rt].le;
int ri=tree[rt].ri;
if(le>=L&&ri<=R){
tree[rt].val+=v;
tree[rt].lz+=v;
return;
}
if(le>R||ri<L){
return;
}
pushdown(rt);
add(rt*2,L,R,v);
add(rt*2+1,L,R,v);
pushup(rt);
}
int get(int rt,int L,int R){
int le=tree[rt].le;
int ri=tree[rt].ri;
if(le>=L&&ri<=R){
return tree[rt].val;
}
if(le>R||ri<L){
return 0;
}
pushdown(rt);
int ret=0;
ret=max(ret,get(rt*2,L,R));
ret=max(ret,get(rt*2+1,L,R));
pushup(rt);
return ret;
}
int main(){
ios::sync_with_stdio(false);
int n,m;
cin >>n >>m;
for(int i=1;i<=n;i++){
cin >> stu[i].le>>stu[i].ri;
h[stu[i].le].push_back(stu[i].ri);
}
ans=max(0,n-m);
build(1,0,m+1);
for(int i=0;i<=m+1;i++){
for(int j=0;j<h[i].size();j++){
int to=h[i][j];
add(1,0,to,1);
}
ans=max(ans,get(1,i+1,m+1)-i-m-1);
}
cout<<ans;
}