CF1834 D. Survey in Class [离线+权值线段树]

文章讲述了如何通过枚举区间并利用贪心策略和线段树数据结构解决一个关于区间高度贡献的问题,涉及到区间分类、离线处理和离散化技术的应用。
摘要由CSDN通过智能技术生成

传送门:CF

[前题提要]:思维难度不高,但感觉维护的技巧性较强,故记录一下


不难想到枚举每一个区间作为我们的高度最大的区间.
这样我们的问题就变成了对于剩下的其他区间,如何找到高度最小的区间.然后对于每一种情况,都统计一下贡献即可.

仔细推敲之后,不难发现,我们只有将当前的区间中的所有数都提问一次,此时的是最优的.为什么呢,下面来简单证明一下这个贪心.不妨假设当前的区间为 [ l , r ] [l,r] [l,r],那么对于在区间外的数字,我们不应该将其加入我们的提问数字,因为对于区间外的数字,我们的最大值区间并不会减少1贡献,对于其他区间来说,他们有两种情况,要么因为此增加1贡献,要么因为此减少1贡献,我们会发现,不管是哪种情况,我们此时的 M a x − M i n Max-Min MaxMin都不会增加.所以此时是不优的.类似的,我们会发现对于区间内的数字,如果我们不提问该数字,我们Max区间会减少1贡献,其他区间要么减少1贡献,要么不减,无论那种情况,发现都是不优.

所以对于枚举的区间 [ l , r ] [l,r] [l,r],我们此时的提问数字必然是 l , l + 1 , . . . , r l,l+1,...,r l,l+1,...,r,考虑此时如何找到最小贡献区间.我们得用 l o g log log以内的算法找到该区间,不难想到二分.所以按照套路,先对所有区间按左端点进行排序,会发现存在大致四种情况.在分类之前,我们得先简单分析一下对于另外一个区间 [ l ′ , r ′ ] [l',r'] [l,r],我们的贡献是什么.假设我们的 [ l , r ] [l,r] [l,r]长度为 x x x,两个区间交叉部分为 y y y,我们此时的贡献就是 x − ( y − ( x − y ) ) x-(y-(x-y)) x(y(xy)),也就是 2 ∗ x − 2 ∗ y 2*x-2*y 2x2y.ok,有了这个之后,我们就可以进行分类了.

  1. 区间 [ l ′ , r ′ ] [l',r'] [l,r], l ′ > r ∣ ∣ r ′ < l l'>r||r'<l l>r∣∣r<l,也就是和上述区间没有交叉
  2. 区间 [ l ′ , r ′ ] [l',r'] [l,r], l ′ ≥ l & & l ′ ≤ r & & r ′ ≥ l & & r ′ ≤ r l'\geq l\&\&l'\leq r \&\&r'\geq l\&\&r'\leq r ll&&lr&&rl&&rr,也就是被上述区间包含的情况
  3. 区间 [ l ′ , r ′ ] [l',r'] [l,r], l ′ ≥ l & & l ′ ≤ r & & r ′ ≥ r l'\geq l\&\&l'\leq r \&\&r'\geq r ll&&lr&&rr,部分交叉
  4. 区间 [ l ′ , r ′ ] [l',r'] [l,r], l ′ ≤ l & & r ′ ≥ l & & r ′ ≤ r l'\leq l \&\&r'\geq l\&\&r'\leq r ll&&rl&&rr,部分交叉

对于第一种情况,很好解决,我们会发现显然贡献就是 2 ∗ x 2*x 2x,当然这种情况,也可以融入到 3 , 4 3,4 3,4一起进行解决.

对于第二种情况,我们会发现这种情况下的所有区间的右端点都是小于等于当前端点的.单单的二分难以解决这种情况,因为我们既需要关注左端点,又需要关注右端点.对于这种情况,我们可以对其进行离线.考虑对排完序后的序列从右往左进行枚举,然后使用权值线段树来维护.因为我们是从右往左进行枚举的,所以对于当前区间,我们所有满足左端点大于等于当前左端点的区间被记录.所以此时我们可以对所有区间的右端点来建一颗权值线段树,然后节点的贡献就是当前区间的长度.那么对于枚举到的区间来说,我们找到右端点小于当前右端点的所有节点的贡献的最小值即可.当前这种情况,整个区间都属于交叉部分,所以显然越小越好.

对于第三种情况,我们继承第二种情况的思考方式.同样从右往左加入区间,那么此时我们只要找到右端点大于等于当前区间的右端点的所有区间中左端点最大的那一个即可.此时我们就可以用这个左端点来计算出交叉部分.同样可以对右端点建一颗权值线段树,节点的贡献就是每一个区间的左端点,这样就可以轻松维护了.

对于第四种情况,维护方式同2,3.但是此时我们得从左往右进行枚举了.但是大致维护方式相同,所以就不再赘述了.

因为区间范围达到了 1 e 9 1e9 1e9,所以需要进行离散化.


下面是具体的代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 200010
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Line{
	int l,r,id;
	bool operator < (const Line &rhs) const {
		if(l!=rhs.l) return l<rhs.l;
		else return r<rhs.r;
	}
}line[maxn];vector<int>v;
int find_pos(int x) {
	auto pos=lower_bound(v.begin(),v.end(),x)-v.begin()+1;
	return pos;
}
struct Segment_tree{
	int l,r,mx,mn;
}tree1[maxn<<2],tree2[maxn<<2];
void pushup(int rt,Segment_tree tree[]) {
	tree[rt].mx=max(tree[ls].mx,tree[rs].mx);
	tree[rt].mn=min(tree[ls].mn,tree[rs].mn);
}
void build(int l,int r,int rt,Segment_tree tree[]) {
	tree[rt].l=l;tree[rt].r=r;tree[rt].mx=-ll_INF;tree[rt].mn=ll_INF;
	if(l==r) {
		return ;
	}
	int mid=(l+r)>>1;
	build(lson,tree);build(rson,tree);
	pushup(rt,tree);
}
void update(int pos,int val,int rt,Segment_tree tree[]) {
	if(tree[rt].l==pos&&tree[rt].r==pos) {
		tree[rt].mx=max(tree[rt].mx,val);
		tree[rt].mn=min(tree[rt].mn,val);
		return ;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(pos<=mid) update(pos,val,ls,tree);
	else update(pos,val,rs,tree);
	pushup(rt,tree);
}
int query1(int l,int r,int rt,Segment_tree tree[]) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		return tree[rt].mn;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) return query1(l,r,ls,tree);
	else if(l>mid) return query1(l,r,rs,tree);
	else return min(query1(l,mid,ls,tree),query1(mid+1,r,rs,tree));
}
int query2(int l,int r,int rt,Segment_tree tree[]) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		return tree[rt].mx;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) return query2(l,r,ls,tree);
	else if(l>mid) return query2(l,r,rs,tree);
	else return max(query2(l,mid,ls,tree),query2(mid+1,r,rs,tree));
}
signed main() {
	int T=read();
	while(T--) {
		int n=read();int m=read();
		for(int i=1;i<=n;i++) {
			line[i].l=read();line[i].r=read();line[i].id=i;
			v.push_back(line[i].l);v.push_back(line[i].r);
		}
		sort(line+1,line+n+1);
		sort(v.begin(),v.end());
		v.erase(unique(v.begin(),v.end()),v.end());
		//right
		int Size=v.size();
		build(1,Size,1,tree1);build(1,Size,1,tree2);
		int ans=-ll_INF;
		for(int i=n;i>=1;i--) {
			int num=2*(line[i].r-line[i].l+1);
			int pos=find_pos(line[i].r);
			int num2=query2(pos,Size,1,tree2);
			int num3=query1(1,pos,1,tree1);
			if(num2>line[i].r) {
				if(num2!=-ll_INF) ans=max(ans,num);
			}
			else {
				if(num2!=-ll_INF) ans=max(ans,num-2*(line[i].r-num2+1));
			}
			if(num3!=ll_INF) ans=max(ans,num-2*num3);
			update(pos,line[i].r-line[i].l+1,1,tree1);
			update(pos,line[i].l,1,tree2);
		}
		//left
		build(1,Size,1,tree1);build(1,Size,1,tree2);
		for(int i=1;i<=n;i++) {
			int num=2*(line[i].r-line[i].l+1);
			int pos=find_pos(line[i].l);
			int num2=query1(1,pos,1,tree1);
			if(num2<line[i].l) {
				if(num2!=ll_INF) ans=max(ans,num);
			}
			else {
				if(num2!=ll_INF) ans=max(ans,num-2*(num2-line[i].l+1));
			}
			update(pos,line[i].r,1,tree1);
		}
		cout<<ans<<endl;
		v.clear();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值