Exhausted? 题解(线段树)

发布时间 2023-11-05 22:27:17作者: 铃狐sama

Exhausted? 题解

前言:

看本篇题解,您如果想要掌握所有知识点的话,请您先去了解下什么是霍尔定理,当然如果可以的话,可以去看看我的这个博客

涉及的算法和思想知识点:

  1. 线段树、扫描线。
  2. 霍尔定理。
  3. 较少的容斥原理。

正文:

理论分析:

  1. 从简单入手:我们想想,要是值域再小一点的话,我们可以怎么做?我的想法是直接把人和合理的区间各个位置连边,形成一个二分图,求解最大匹配找到最多的能不添加椅子就可以做的人,然后用总共的减去就是最小的了。

  2. 思维拔升:我们发现不可以建立图时,不然就是算法有问题不然就是要用各种理论来简化,对于二分图而言,一般建不了图,就是霍尔定理。我们将人看做 \(V1\),根据定理,那么我们就是求解 \(\max(|S1|-|S2|)\) 这个柿子。

  3. 举例化简:现在我们随便选择两个点作为 \(S1\) 那么就是求解他们 \(S2\) 的并。我们稍微容斥一下,变为求解两个点区间的补集的交集的补集。这么说很混乱,我直接举例子吧,就是求 \([L1+1,R1-1]\)\([L2+1,R2-1]\) 的交集的补集。我们用柿子表达,就是求 \(\max(|S1|-m+R-L+1)\)。注意下,要是没有交集,答案就是 \(n-m\),所以答案至少为它。

  4. 思考解决最值的方向:我们为了让上述柿子最大,像这种题目不然就是贪心,不然就是找函数最值,不然就是硬着头皮想方法维护。贪心的话我实在想不出来,用函数的话,我们会发现我们点选得越多,\(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;
	
}