脑洞治疗仪(线段树二分)

脑洞治疗仪

曾经发明了自动刷题机的发明家 SHTSC 又公开了他的新发明:脑洞治疗仪——一种可以治疗他因为发明而日益增大的脑洞的神秘装置。

为了简单起见,我们将大脑视作一个 0/1 序列。1代表这个位置的脑组织正常工作,0代表这是一块脑洞。

1 0 1 0 0 0 1 1 1 0

脑洞治疗仪修补某一块脑洞的基本工作原理就是将另一块连续区域挖出,将其中正常工作的脑组织填补在这块脑洞中。(所以脑洞治疗仪是脑洞的治疗仪?)

例如,用上面第8号位置到第10号位置去修补第1号位置到第4号位置的脑洞,我们就会得到:

1 1 1 1 0 0 1 0 0 0

如果再用第1号位置到第4号位置去修补第8号位置到第10号位置:

0 0 0 0 0 0 1 1 1 1

这是因为脑洞治疗仪会把多余出来的脑组织直接扔掉。

如果再用第7号位置到第10号位置去填补第1号位置到第6号位置:

1 1 1 1 0 0 0 0 0 0

这是因为如果新脑洞挖出来的脑组织不够多,脑洞治疗仪仅会尽量填补位置比较靠前的脑洞。

假定初始时 SHTSC 并没有脑洞,给出一些挖脑洞和脑洞治疗的操作序列,你需要即时回答 SHTSC 的问题:在大脑某个区间中最大的连续脑洞区域有多大。

输入格式

第一行两个整数 n、m,表示 SHTSC 的大脑可分为从1到n编号的n个连续区域,有m个操作。

以下mm行每行是下列三种格式之一:

0 l r:SHTSC 挖了一个范围为[l,r]的脑洞。

1 l0 r0 l1 r1​:SHTSC 进行了一次脑洞治疗,用从l0​到r0​的脑组织修补l1​到r1​的脑洞。

2 l r:SHTSC 询问[l,r]区间内最大的脑洞有多大。

上述区间均在[1,n]范围内。

输出格式

对于每个询问,输出一行一个整数,表示询问区间内最大连续脑洞区域有多大。

解法

用线段树(不要用zkw),每个节点存区间中最左端连续的零的个数、最右端连续的零的个数,整个区间最长的连续零序列长度和序列中1的个数,
考虑填脑洞时一定要直接在线段树上找,不能用二分加线段树,因为线段树本就是基于二分原理的,何不就用O(logn)的查找呢?
区间加法详见代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#define LL long long
using namespace std;

int read() {
	int f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s == '-')f = -1;s = getchar();}
	while(s >= '0' && s <= '9') {x = x * 10 + s - '0';s = getchar();}
	return x * f;
}
struct tr{
	int l,r;
	int c;
	int p,s,m;
}tre[800005],tr0;
int lz[800005];
tr operator + (tr a,tr b) {
	if(a.l > a.r) return b;
	if(b.l > b.r) return a;
	tr c;
	c.l = a.l,c.r = b.r;
	c.c = a.c + b.c;
	c.p = (a.p == a.r - a.l + 1 ? a.p + b.p : a.p);
	c.s = (b.s == b.r - b.l + 1 ? b.s + a.s : b.s);
	c.m = a.s + b.p;
	c.m = max(a.p,max(a.m,max(a.s,max(b.m,max(b.s,max(b.p,max(c.p,max(c.s,c.m))))))));
	return c;
} 
void maketree(int l,int r,int a) {
	tre[a].l = l;
	tre[a].r = r;
	tre[a].c = r - l + 1;
	tre[a].p = tre[a].s = tre[a].m = 0;
	if(l < r) {
		int mid = (l + r) / 2;
		maketree(l,mid,a * 2);
		maketree(mid + 1,r,a * 2 + 1);
	}
	return ;
}
void chg(int a,int y) {
	tre[a].c = (tre[a].r - tre[a].l + 1) * y;
	tre[a].p = (tre[a].r - tre[a].l + 1) * (1 - y);
	tre[a].m = tre[a].s = tre[a].p;
	lz[a] = y;
	return ;
}
void addtree(int l,int r,int a,int y) {
	if(l > r) return ;
	if(l > tre[a].r || r < tre[a].l) {
		return ;
	}
	if(l <= tre[a].l && r >= tre[a].r) {
		chg(a,y);
		return ;
	}
	if(lz[a] >= 0) {
		chg(a * 2,lz[a]);
		chg(a * 2 + 1,lz[a]);
		lz[a] = -1;
	}
	addtree(l,r,a * 2,y);
	addtree(l,r,a * 2 + 1,y);
	tre[a] = tre[a * 2] + tre[a * 2 + 1];
	return ;
}
tr findt(int l,int r,int a) {
	if(l > r) return tr0;
	if(l > tre[a].r || r < tre[a].l) {
		return tr0;
	}
	if(l <= tre[a].l && r >= tre[a].r) {
		return tre[a];
	}
	if(lz[a] >= 0) {
		chg(a * 2,lz[a]);
		chg(a * 2 + 1,lz[a]);
		lz[a] = -1;
	}
	return findt(l,r,a * 2) + findt(l,r,a * 2 + 1);
}
int solve(int l,int r,int a,int &w) {
	if(l > tre[a].r || r < tre[a].l) {
		return r;
	}
	if(l <= tre[a].l && r >= tre[a].r && tre[a].r - tre[a].l + 1 - tre[a].c <= w) {
		w -= tre[a].r - tre[a].l + 1 - tre[a].c;
		return tre[a].r;
	}
	if(lz[a] >= 0) {
		chg(a * 2,lz[a]);
		chg(a * 2 + 1,lz[a]);
		lz[a] = -1;
	}
	int ls = solve(l,r,a * 2,w);
	if(w == 0) return ls;
	return solve(l,r,a * 2 + 1,w);
} 
int n,m,i,j,s,o,k;
int main() {
	n = read();m = read();
	tr0.l = 2;tr0.r = 1;
	tr0.c = 0;
	tr0.m = 0;
	memset(lz,-1,sizeof(lz));
	maketree(1,n,1);
	for(int i = 1;i <= m;i ++) {
		k = read();
		if(k == 0) {
			s = read();o = read();
			addtree(s,o,1,0);
		}
		else if(k == 1) {
			s = read();o = read();
			tr ast = findt(s,o,1);
			int as = ast.c;
			addtree(s,o,1,0);
			s = read();o = read();
			int aas = as;
			int rr = solve(s,o,1,aas);
			if(as)addtree(s,rr,1,1);
		}
		else if(k == 2) {
			s = read();o = read();
			tr ast = findt(s,o,1);
			int as = ast.m;
			printf("%d\n",as);
		}
	}
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值