线段树学习

目录

线段树

洛谷P3372 【模板】线段树 1

POJ - 3264 Balanced Lineup

POJ - 2528 Mayor's posters

HDU - 1540 Tunnel Warfare

洛谷P4513 小白逛公园

HDU - 4614 Vases and Flowers

HDU - 4553 约会安排

洛谷P2824 [HEOI2016/TJOI2016] 排序


随缘更新,如果看到有些比较好的题的话

线段树

解决各种区间问题

线段树主要是利用分治的思想来将整体区间划分为各个小区间,然后再在这些区间上进行相应的操作,这里以最基本的区间加和区间求和为例

首先是线段树的结构:线段树的每一个结点都存储着一段区间的信息,该结点的孩子结点存储着该区间的子区间的信息

以区间[1,2,3,4,5,6,7,8]为例,将区间拆分成[1,2,3,4]和[5,6,7,8],再将区间拆分成[1,2],[3,4],[5,6],[7,8],最后将他们拆分成1,2,3,4,5,6,7,8八个区间,这里的每一个区间都是线段树的一个结点,所以线段树的空间占用较大,一般数组要开四倍

[1, 2, 3, 4 ,  5, 6, 7, 8]

[1, 2, 3, 4]  [5, 6, 7, 8]

[1, 2][3, 4] [5, 6][7, 8]

[1][2][3][4][5][6][7][8]

下面是建树的过程:建树操作一般是利用递归自底向上建树,也就是不断地分割区间当区间长度为1时就可保证该区间的值就是该数的值,这里用now来表示当前结点所在位置,l、r表示区间,可以知道当前结点的值为左右孩子结点的值,也就是要先建左右区间然后在向上求和,代码如下:

int a[100010];
int tree[100010*4];
int tag[100010*4];
void push_up(int now)
{
	tree[now] = tree[now<<1] + tree[now<<1|1];
}
void build(int now,int l,int r)
{
	tag[now] = 0;
	if(l == r)//如果相等则表示区间长度为1
	{
		tree[now] = a[l];
		return;
	}
	int mid = l+r>>1;//不等则将区间分为两部分,分别建立
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);//最后对该结点求和
}

然后就是最核心的更新操作

对一个区间整体加上x,显然可知这个区间的值增加了(区间长度*x),那么如何知道子区间是否增加了,似乎只能继续递归左右求解下去,但是这样如果后面区间没有被询问到就会浪费了很多时间,这里就需要引入一个新的东西——懒标记

所谓懒标记就是在区间加操作的时候先不进行向下传递,而是先对其进行存储,查询到该区间时再传递给下一层结点

传递时要注意,tag存储的是区间整体加的值x,所以传递给下一个区间时该区间增加的值为该区间长度*x,同时要将原区间的tag清空,避免下次再次调用造成错误答案

然后就是主要的更新部分,如果一个区间不满足任意一个结点的长度该怎么办

其实很简单,我们知道,大的区间一定包含小的区间,所以我们可以将多段区间拼起来成为一个大的区间,根据线段树的结构,我们可以通过搜索左孩子来找到左端点,搜索右孩子来找到右端点,将两个端点之间的所有结点全部更新即可

假设当前区间为[1,8],要更新[2,7]这个区间,可以通过左孩子找到[1,4]->[1,2][3,4]->[2][3,4]和通过右孩子找到[5,8]->[5,6][7,8]->[5,6][7],更新这四个区间即可,每次的操作复杂度平均为logn

[1, 2, 3, 4 ,  5, 6, 7, 8]

[1, 2, 3, 4]  [5, 6, 7, 8]

[1, 2][3, 4] [5, 6][7, 8]

[1][2][3][4][5][6][7][8]

最后要记得每次更新完要向上传递一下结点的值来更新父节点

具体看代码注释


void push_down(int now,int l,int r)
{
    int mid = l+r>>1;
	tag[now<<1] += tag[now];
	tree[now<<1] += (mid-l+1) * tag[now];//更新左区间
	tag[now<<1|1] += tag[now];
	tree[now<<1|1] += (r-mid) * tag[now];//更新右区间
	tag[now] = 0;//将原区间标记清空
}

//now当前结点,l,r查询区间,nl,nr当前区间,num区间加值
void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(l <= nl && r >= nr)//如果被查询区间已经在当前区间内,直接操作即可
	{
		tag[now] += num;
		tree[now] += (nr-nl+1) * num;
		return;
	}
	int mid = nl+nr>>1;
	push_down(now,nl,nr);//懒标记下放
	if(l <= mid)//被查询区间的左端点小于等于当前区间的中点
	{
		updata(now<<1,l,r,nl,mid,num);//向左查询
	}
	if(r > mid)//被查询区间的右端点大于当前区间的中点
	{
		updata(now<<1|1,l,r,mid+1,nr,num);//向右查询
	}
	push_up(now);//记得上传更新新的值
}

最后就是查询操作

和区间更新操作一样,仍然是将大区间转化为小区间求和,一步步找到一个线段树的结点且是查询区间的子区间的时候就可以纳入答案,最后的结点之和即为大区间和

//now当前结点,l,r查询区间,nl,nr当前区间
int query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];//如果区间被包含直接返回即可
	}
	int mid = nl+nr>>1;
	push_down(now,nl,nr);//这里也要下放懒标记
	int ans = 0;
	if(l <= mid)
	{
		ans += query(now<<1,l,r,nl,mid);//向左查询
	}
	if(r > mid)
	{
		ans += query(now<<1|1,l,r,mid+1,nr);//向右查询
	}
	return ans;
}

整体代码:

#include<iostream>
#include<map>
#include<queue>
#include<vector>
#include<algorithm>
#include<set>
#include<cstring>
#include<cmath>
using namespace std;

int cnt = 1;
int a[50010];
int tree[100010*4];
int tag[100010*4];
void push_up(int now)
{
	tree[now] = tree[now<<1] + tree[now<<1|1];
}
void build(int now,int l,int r)
{
	tag[now] = 0;
	if(l == r)
	{
		tree[now] = a[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
}
void push_down(int now,int l,int r)
{
    int mid = l+r>>1;
	tag[now<<1] += tag[now];
	tree[now<<1] += (mid-l+1) * tag[now];
	tag[now<<1|1] += tag[now];
	tree[now<<1|1] += (r-mid) * tag[now];
	tag[now] = 0;
}
void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(l <= nl && r >= nr)
	{
		tag[now] += num;
		tree[now] += (nr-nl+1) * num;
		return;
	}
	int mid = nl+nr>>1;
	push_down(now,nl,nr);
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
	push_up(now);
}
int query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
	push_down(now,nl,nr);
	int ans = 0;
	if(l <= mid)
	{
		ans += query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		ans += query(now<<1|1,l,r,mid+1,nr);
	}
	return ans;
}
void solve()
{
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
	build(1,1,n);
	char str[10];
	while(~scanf("%s",str))
	{
		if(str[0] == 'E') break;
		if(str[0] == 'Q')
		{
			int l,r;
			scanf("%d %d",&l,&r);
			cout << query(1,l,r,1,n) << "\n";
		}
		else if(str[0] == 'A')
		{
			int l,r,num;
			scanf("%d %d %d",&l,&r,&num);
			updata(1,l,r,1,n,num);
		}
		else if(str[0] == 'S')
		{
			int l,r,num;
			scanf("%d %d %d",&l,&r,&num);
			updata(1,l,r,1,n,-num);
		}
	}
	return;
}
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		solve();
	}
	return 0;
}

这里放个模板:

洛谷P3372 【模板】线段树 1


POJ - 3264 Balanced Lineup

题目大意:给出n头牛按序号1-n排好序,每头牛有身高ai,q次询问每次求区间[l,r]中最最高和最低的牛的身高差

RMQ问题,有很多种方法解决,这里介绍线段树的写法

思路:

建立一颗线段树,每个结点分别存储该节点所代表的区间内的牛的身高的最大值和最小值

那么该结点的两个值即为左孩子和右孩子中的maxnum的最大值与左孩子和右孩子中minnum的最小值

建树代码如下:

int a[50010];
struct node{
	int minnum,maxnum;
}tree[50010*4];

void push_up(int now)
{
    //两子区间的最大值为该区间内最大值
	tree[now].minnum = min(tree[now<<1].minnum,tree[now<<1|1].minnum);
    //两子区间的最小值为该区间内最小值
	tree[now].maxnum = max(tree[now<<1].maxnum,tree[now<<1|1].maxnum);
	return;
}
void build(int now,int l,int r)
{
	if(l == r)
	{
        //初始时单个区间内最大最小值都相同
		tree[now].minnum = a[l];
		tree[now].maxnum = a[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
	return;
}

同样也可以建立两颗非结构体类型的线段树

一颗维护区间最大值,另一颗维护区间最小值,原理相同

由于此题没有涉及到区间修改,所以这里不展示区间修改的代码,基本原理和普通线段树相同,只是没有涉及到懒标记

然后是区间查询操作

我们已知线段树的一个结点代表的是一个区间的最大和最小值,就是我们最终想要的答案,所以我们考虑查询结果直接返回一个结点

和普通线段树相同,每次继续分区间查询的时候更新一下结点的值即可

代码如下:

node query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
	node p;
    //注意初始化最大和最小值
	p.minnum = 0x3f3f3f3f;
	p.maxnum = -0x3f3f3f3f;
	if(l <= mid)
	{
        //往左区间查询时用返回的结点更新p
		node q = query(now<<1,l,r,nl,mid);
		p.minnum = min(p.minnum,q.minnum);
		p.maxnum = max(p.maxnum,q.maxnum);
	}
	if(r > mid)
	{
        //往右区间查询时也要更新p
		node q = query(now<<1|1,l,r,mid+1,nr);
		p.minnum = min(p.minnum,q.minnum);
		p.maxnum = max(p.maxnum,q.maxnum);
	}
    //最后返回一个结点p即可
	return p;
}

最后输出的答案就是我们查询返回得到的结点的最大值减去最小值

完整代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<cstring>
using namespace std;


int a[50010];
struct node{
	int minnum,maxnum;
}tree[50010*4];

void push_up(int now)
{
	tree[now].minnum = min(tree[now<<1].minnum,tree[now<<1|1].minnum);
	tree[now].maxnum = max(tree[now<<1].maxnum,tree[now<<1|1].maxnum);
	return;
}
void build(int now,int l,int r)
{
	if(l == r)
	{
		tree[now].minnum = a[l];
		tree[now].maxnum = a[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
	return;
}
node query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
	node p;
	p.minnum = 0x3f3f3f3f;
	p.maxnum = -0x3f3f3f3f;
	if(l <= mid)
	{
		node q = query(now<<1,l,r,nl,mid);
		p.minnum = min(p.minnum,q.minnum);
		p.maxnum = max(p.maxnum,q.maxnum);
	}
	if(r > mid)
	{
		node q = query(now<<1|1,l,r,mid+1,nr);
		p.minnum = min(p.minnum,q.minnum);
		p.maxnum = max(p.maxnum,q.maxnum);
	}
	return p;
}
int main()
{
	int n,q;
	scanf("%d %d",&n,&q);
	for(int i = 1;i <= n;i++)
	{
		scanf("%d",&a[i]);
	}
	build(1,1,n);
	for(int i = 1;i <= q;i++)
	{
		int l,r;
		scanf("%d %d",&l,&r);
		node res = query(1,l,r,1,n);
		printf("%d\n",res.maxnum-res.minnum);
	}
	return 0;
}

POJ - 2528 Mayor's posters

题目大意:有一个长10000000的墙壁,现在有n张海报要贴,每张海报贴在Li到Ri的位置上,后来的海报会覆盖原来的海报的一部分,求贴完所有海报后能看见的海拔有多少种(不需要完全看见,只要没被完全覆盖就算)

可以先写这道简单的再来看这题  ZOJ - 1610 Count the Colors

思路:

区间覆盖问题,很明显可以用线段树的懒标记来操作

每次给区间打上一个懒标记来记录改区间目前的海拔是第i种

每次更新和查询的时候下放最后看每个长度为1的区间的海报种类并且记录数量即可

注意本题的墙壁长度,为10000000,但是海报的数量是1000000范围内

所以我们可以考虑对海报的端点进行离散化

但此时我们要考虑一个问题:离散化后的点都是连续的,但是实际并不是连续的

例如:[1,10] [1,3] [6,10]这三张海报离散化后是[1,4] [1,2] [3,4]

很明显原区间最后结果是3,而离散化后的答案为2

所以我们考虑在离散化的时候对每个差距不为1的距离之中再插入一个中间值

也就是上面的1 3 6 10在操作后变为1 2 3 4 6 7 10

这样离散化后的区间就是[1,7] [1,3] [5,7]此时不连续结果正确

离散化操作代码如下:

int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
	scanf("%d %d",&a[i],&b[i]);
    //这里用rank数组来记录所有的端点
	rk[i] = a[i];
	rk[i+n] = b[i];
}
//进行第一次排序,目的是为了后面去重
sort(rk+1,rk+1+2*n);
int cnt = 1;
for(int i = 2;i <= 2*n;i++)
{
    //此步操作为去重
    //原理:当前值和前一个的值不相等则记录,否则不记录
	if(rk[i] != rk[i-1])
	{
		rk[++cnt] = rk[i];
	}
}
for(int i = cnt;i >= 1;i--)
{
    //此步操作为插入中间值
	if(rk[i] != rk[i-1]+1)
	{
		rk[++cnt] = rk[i-1]+1;
	}
}
//第二次排序,后面用l,r值时二分查找找到下标即可
sort(rk+1,rk+1+cnt);

然后是更新操作,由于是线段染色,所以不需要维护区间的其他信息,只维护区间当前的标记即可,每次二分向下更新时将标记下放即可

代码如下:

其实这里的tree就是普通线段树里的tag数组,因为不需要维护其他区间信息
void push_down(int now)
{
	tree[now<<1] = tree[now];
	tree[now<<1|1] = tree[now];
	tree[now] = 0;
}
//更新操作和模板基本相同
void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(l <= nl && r >= nr)
	{
		tree[now] = num;
		return;
	}
	int mid = nl + nr >> 1;
	if(tree[now] != 0) push_down(now);
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
}

最后是查询操作,这里不需要遍历1~输入的最大的r来一步步查询,可以用类似于建树的方法不断地二分区间来找到所有单个的区间,l==r时记录答案并且返回即可,由于答案可能会重复,这里用map来记录是否已出现过某个海报

总体代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<stdio.h>
#include<set>
#include<queue>
#include<cstring>
using namespace std;


int ans;
int a[100010];
int b[100010];
int rk[300010];
int tree[400010<<3];
map<int,bool> mp; 
void push_down(int now)
{
	tree[now<<1] = tree[now];
	tree[now<<1|1] = tree[now];
	tree[now] = 0;
}
void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(l <= nl && r >= nr)
	{
		tree[now] = num;
		return;
	}
	int mid = nl + nr >> 1;
	if(tree[now] != 0) push_down(now);
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
}

void query(int now,int l,int r)
{
	if(l == r)
	{
		if(mp[tree[now]] || tree[now] == 0) return;
		else
		{
			ans++;
			mp[tree[now]] = 1;
			return;
		}
	}
	if(tree[now] != 0) push_down(now);
	int mid = l + r >> 1;
	query(now<<1,l,mid);
	query(now<<1|1,mid+1,r);
}
void solve()
{
	memset(tree,0,sizeof(tree));
	memset(rk,0,sizeof(rk));
	ans = 0;
	mp.clear();
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)
	{
		scanf("%d %d",&a[i],&b[i]);
		rk[i] = a[i];
		rk[i+n] = b[i];
	}
	sort(rk+1,rk+1+2*n);
	int cnt = 1;
	for(int i = 2;i <= 2*n;i++)
	{
		if(rk[i] != rk[i-1])
		{
			rk[++cnt] = rk[i];
		}
	}
	for(int i = cnt;i >= 1;i--)
	{
		if(rk[i] != rk[i-1]+1)
		{
			rk[++cnt] = rk[i-1]+1;
		}
	}
	sort(rk+1,rk+1+cnt);
	int len = cnt;
	cnt = 0;
	for(int i = 1;i <= n;i++)
	{
		int l = lower_bound(rk+1,rk+1+len,a[i]) - rk;
		int r = lower_bound(rk+1,rk+1+len,b[i]) - rk;
		updata(1,l,r,1,len,++cnt);
	}
	query(1,1,len);
	printf("%d\n",ans);
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
	return 0;
}

HDU - 1540 Tunnel Warfare

题目大意:有n个村子,每个村子都与他左右两边的村子相连(端点只连一边),每次操作可以破坏一个村子或者恢复上一个被破坏的村子,每次询问一个数x,求与x相连的村子有多少(包含本身)

思路:

连续区间长度问题,断开一个点就代表将一个区间分成了两个小区间

可以用1来代表没被摧毁,0来代表被摧毁,求一个村子的连接数量只需要分别找到左边和右边的第一个0的位置即可

接下来考虑如何操作才能找到左右两边的0

既然我们用1和0来代表村子是否被摧毁,那么就意味着:

如果一个区间的和不等于区间的长度,说明该区间内有村子被摧毁。

我们可以根据这一条信息先考虑前缀和,很明显前缀和是单调不减的

可以利用二分的方法来找到某点左边的第一个0和右边的第一个0(包含自己)

具体操作如下:

  • 对左边区间进行二分,优先考虑二分后的右边的区间,因为这部分离x点更近,如果区间和小于区间长度则表示该区间有0,继续查找该区间,否则去查找另一个区间
  • 对右边区间进行二分,优先考虑二分后的左边的区间,同理若区间和小于区间长度则继续查找,否则查找另一个区间

这里特判一下左右区间长度为1的情况,也就是该区间为左端点或右端点时的情况

  • 若为左端点且区间和为1,则返回0,否则返回1
  • 若为右端点且区间和为1,则返回n+1,否则返回n

本题还包含单点修改操作,那么简单的前缀和肯定是完成不了任务的

所以考虑用线段树来维护区间和进行操作,二分直接在线段树上二分即可

对于恢复村庄的操作我们可以用栈来存储被摧毁的村庄,每次恢复最后一个入栈的村庄即可

整体代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<set>
#include<queue>
#include<cstring>
using namespace std;

int n,m;
int tree[50010*4];
void push_up(int now)
{
	tree[now] = tree[now<<1] + tree[now<<1|1];
	return;
}
void build(int now,int l,int r)
{
	if(l == r)
	{
		tree[now] = 1;
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
}
void updata(int now,int l,int r,int nl,int nr,int x)
{
	if(nl == nr)
	{
		tree[now] = x;
		return;
	}
	int mid = nl+nr>>1;
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,x);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,x);
	}
	push_up(now);
	return;
}
int query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
	int ans = 0;
	if(l <= mid)
	{
		ans += query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		ans += query(now<<1|1,l,r,mid+1,nr);
	}
	return ans;
}
int lpos(int l,int r)
{
    //向左查找第一个0的过程
	if(l == r)
	{
		return query(1,l,r,1,n) == 1 ? l-1 : l;
	}
	while(l <= r)
	{
		int mid = l+r>>1;
		if(query(1,mid,r,1,n) < r-mid+1)
		{
			l = mid + 1;
		}
		else
		{
			r = mid - 1;
		}
	}
    //这里返回的是r而不是l,因为只有右区间中没有0的时候r才会改变
	return r;
}
int rpos(int l,int r)
{
    //向右查找第一个0的过程
	if(l == r)
	{
		return query(1,l,r,1,n) == 1 ? l+1 : l;
	}
	while(l <= r)
	{
		int mid = l+r>>1;
		if(query(1,l,mid,1,n) < mid-l+1)
		{
			r = mid - 1;
		}
		else
		{
			l = mid + 1;
		}
	}
    //这里返回的是l,因为只有左区没有为0的时候l才会改变
	return l;
}
int main()
{
	while(~scanf("%d %d",&n,&m))
	{	
		int x;
		char ch;
		stack<int> st;
		build(1,1,n);
		for(int i = 1;i <= m;i++)
		{
			scanf(" %c",&ch);
			if(ch == 'D')
			{
				scanf("%d",&x);
				updata(1,x,x,1,n,0);
				st.push(x);
			}
			else if(ch == 'Q')
			{
				scanf("%d",&x);
				int l = lpos(1,x);
				int r = rpos(x,n);
                //计算答案
				if(l == r) printf("0\n");
				else printf("%d\n",r-l-1);
			}
			else
			{
				if(st.empty()) continue;
				int x = st.top();
				st.pop();
				updata(1,x,x,1,n,1);
			}
		}
		
		
	}
}

洛谷P4513 小白逛公园

题目大意:n个公园,每个公园有一个分数ai(可能为负数),q次询问每次可能更改一个公园的分数,也可能查询区间[l,r]中的连续的公园的最大分数和

思路:

区间最大连续字段和问题,既然是区间问题,那么就依然可以用线段树来维护

我们先搞清楚答案可能会出现在哪里:

  • 第一种可能,区间最大连续字段和包含区间左端点
  • 第二种可能,区间最大连续最短和包含区间右端点
  • 第三种可能,可能包含端点也可能不包含端点

所以我们可以用线段树来维护这三个值:max_l,max_r,max_sum分别代表包含左端点的区间最大值、包含右端点的区间最大值、实际的区间最大值

建树的过程很简单,因为最小的区间只有一个值,所以这三个值都是ai

当左右区间合并的过程中我们需要考虑到左区间的右端点会和右区间的左端点连接

合并后的区间max_sum有两种可能:

  • 变为左右区间的max_sum的最大值
  • 变为左区间包含右端点的最大值max_r与右区间包含左端点的最大值max_l之和

合并后的区间max_l两种可能:

  • 变为左区间的max_l
  • 变为整个左区间的和加上右区间包含左端点的最大值

合并后的区间max_r有两种可能:

  • 变为右区间的max_r
  • 变为整个右区间加上左区间包含右端点的最大值

这三个变化很好理解,没看明白的随便拿两个线段一拼接就明白了

根据上面的结论我们可以发现还需要额外存储一个变量来记录区间和,这里用sum来记录,维护这个变量的操作就不必多说了

建树代码:

struct node{
	int max_sum,max_l,max_r,sum;
}tree[500010*4];
int a[500010];

void push_up(int now)
{
    //具体变化根据上面三条结论
	int m_sum = max(tree[now<<1].max_sum,tree[now<<1|1].max_sum);
	tree[now].max_l = max(tree[now<<1].max_l,tree[now<<1].sum+tree[now<<1|1].max_l);
	tree[now].max_r = max(tree[now<<1|1].max_r,tree[now<<1|1].sum+tree[now<<1].max_r);
	tree[now].sum = tree[now<<1].sum+tree[now<<1|1].sum;
	tree[now].max_sum = max(m_sum,tree[now<<1].max_r+tree[now<<1|1].max_l);
}
void build(int now,int l,int r)
{
	if(l == r)
	{
        //区间长度为1的时候这些值都是一样的
		tree[now].max_sum = tree[now].sum = tree[now].max_l = tree[now].max_r = a[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
}

然后是区间更新,这题是单点更新,直接找到单个区间后直接修改所有值即可

区间更新代码:

void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(nl == nr)
	{
        //单点更新,所以单个区间所有值都变为num
		tree[now].sum = num;
		tree[now].max_l = num;
		tree[now].max_r = num;
		tree[now].max_sum = num;
		return;
	}
	int mid = nl+nr>>1;
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	else if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
	push_up(now);
}

最后是区间查询,这里我们以结点作为返回值来传递,因为如果用max_num来进行传递的话我们就不能知道查询左右两个区间的时候这两个区间是否能拼接的问题了

用l_node、r_node分别代表左右两个区间查询出来的结点

ans结点表示当前查询结束后要返回的结点,也就是l_node和r_node区间合并后的结点,操作和push_up过程相同

查询代码如下:

node query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
    //这里注意初始话三个最大值为最小 并且sum=0
	node ans = node{-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	node l_node = {-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	node r_node = {-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	if(l <= mid)
	{
		l_node = query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		r_node = query(now<<1|1,l,r,mid+1,nr);
	}
    //ans为左右两个区间的合并区间
	ans.sum = l_node.sum+r_node.sum;
	ans.max_l = max(l_node.max_l,l_node.sum+r_node.max_l);
	ans.max_r = max(r_node.max_r,l_node.max_r+r_node.sum);
	ans.max_sum = max(max(l_node.max_sum,r_node.max_sum),l_node.max_r+r_node.max_l);
	return ans;
}

最后输出答案只需要输出返回结点的max_sum即可

总体代码如下:

#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

struct node{
	int max_sum,max_l,max_r,sum;
}tree[500010*4];
int a[500010];

void push_up(int now)
{
	int m_sum = max(tree[now<<1].max_sum,tree[now<<1|1].max_sum);
	tree[now].max_l = max(tree[now<<1].max_l,tree[now<<1].sum+tree[now<<1|1].max_l);
	tree[now].max_r = max(tree[now<<1|1].max_r,tree[now<<1|1].sum+tree[now<<1].max_r);
	tree[now].sum = tree[now<<1].sum+tree[now<<1|1].sum;
	tree[now].max_sum = max(m_sum,tree[now<<1].max_r+tree[now<<1|1].max_l);
}
void build(int now,int l,int r)
{
	if(l == r)
	{
		tree[now].max_sum = tree[now].sum = tree[now].max_l = tree[now].max_r = a[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
}

void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(nl == nr)
	{
		tree[now].sum = num;
		tree[now].max_l = num;
		tree[now].max_r = num;
		tree[now].max_sum = num;
		return;
	}
	int mid = nl+nr>>1;
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	else if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
	push_up(now);
}
node query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	int mid = nl+nr>>1;
	node ans = node{-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	node l_node = {-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	node r_node = {-0x3f3f3f3f,-0x3f3f3f3f,-0x3f3f3f3f,0};
	if(l <= mid)
	{
		l_node = query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		r_node = query(now<<1|1,l,r,mid+1,nr);
	}
	ans.sum = l_node.sum+r_node.sum;
	ans.max_l = max(l_node.max_l,l_node.sum+r_node.max_l);
	ans.max_r = max(r_node.max_r,l_node.max_r+r_node.sum);
	ans.max_sum = max(max(l_node.max_sum,r_node.max_sum),l_node.max_r+r_node.max_l);
	return ans;
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n,m;
	cin >> n >> m;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}
	build(1,1,n);
	for(int i = 1;i <= m;i++)
	{
		int op,l,r;
		cin >> op >> l >> r;
		if(op == 1)
		{
			if(l > r) swap(l,r);
			node ans = query(1,l,r,1,n);
			cout << ans.max_sum << "\n";
		}
		else if(op == 2)
		{
			updata(1,l,l,1,n,r);
		}
	}
}

HDU - 4614 Vases and Flowers

题目大意:

有n个空盆栽,每次可以对其进行以下操作之一:

  • 选择一个位置A,然后从A开始往后面插F朵花(花只能插在空盆栽上,若盆栽上有花则跳过该盆栽),然后输出插入第一朵花的位置和插入最后一朵能插入的花的位置(若不能插入则输出不能插入)
  • 选择一个区间[l,r],拔掉所有的花,输出拔掉的花的数量

思路:第一种操作实际上是求从A开始找到第一个满足区间内空盆栽数量等于F的区间,如果直到最后一盆区间内数量还是不够则按上面的情况输出,第二种操作实际上就是求区间内有几个花,然后将其情况

考虑用1来代表该位置有花,0表示没有

  • 对于第二种操作实际上就是求区间和,然后将其全置为0
  • 第一种操作是找到从A开始的第一个区间长度不等于区间和的位置p,然后再从p+1开始找到第一个区间长度==区间之和+F-1的位置,因为区间长度-区间和就是区间内0的个数

考虑使用线段树来维护区间和

对于第一种操作,只需要以A为左端点,二分找到第一个使得R1-A+1 == [A,R1]-1的右端点R1

然后再找到一个端点R2使得R2-A +1== [A,R2]-F,R1、R2即为所求

若区间的长度不够,也就是[A,n]的区间和小于F,那么只需要找到最后一个0,然后将区间全置为1

若区间[A,n]没有一个0,也就是[A,n] == n-A+1,那么直接按照不能插入的情况输出

代码如下:

对于第二种操作,只需要用线段树求区间和,然后区间推平为0即可

这里是区间置1或0,所以要有懒标记来记录

void push_down(int now,int l,int r)
{
    //懒标记为-1表示没有标记
	if(tag[now] == -1) return;
	int mid = l+r>>1;
	if(tag[now])//为1表示置1
	{
		tree[now<<1] = (mid-l+1);
		tree[now<<1|1] = (r-mid);
	}
	else//为0表示置0
	{
		tree[now<<1] = 0;
		tree[now<<1|1] = 0;
	}
	tag[now<<1] = tag[now];
	tag[now<<1|1] = tag[now];
	tag[now] = -1;
	return;
}

上面几种查找操作都可以用二分来实现,利用线段树查询区间和的功能和区间和小于区间长度时必包含0区间和等于区间长度时必全为1的性质来进行二分即可

查询代码如下:

int plant(int pos,int x)
{
	if(x == 0) return pos;
	if(pos > n) return n;
	int l = pos,r = n;
    //这里是不能插够x个花的情况
	if((n-pos+1)-query(1,pos,n,1,n) < x)
	{
		while(l < r)
		{
			int mid = l+r+1>>1;
            //找到最右边的0
			if(query(1,mid,n,1,n) == (n-mid+1))
			{
				r = mid - 1;
			}
			else
			{
				l = mid;
			}
		}
	}
	else
	{
        //这里是可以插满x个花的情况
		while(l <= r)
		{
			int mid = l+r>>1;
            //找到第一个满足区间之和等于区间长度-x的位置
			if((mid-pos+1)-query(1,pos,mid,1,n) >= x)
			{
				r = mid - 1;
			}
			else
			{
				l = mid + 1;
			}
		}
	}
    //插花记得把区间置1
	updata(1,pos,l,1,n,1);
	return l;
}

这题要注意端点是可以取到0的,但线段树不能处理端点为0的操作,所有先整体加一最后输出减一即可

整体代码 如下:

#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m;
int tree[50010*4];
int tag[50010*4];
void push_up(int now)
{
	tree[now] = tree[now<<1] + tree[now<<1|1];
	return;
}
void push_down(int now,int l,int r)
{
	if(tag[now] == -1) return;
	int mid = l+r>>1;
	if(tag[now])
	{
		tree[now<<1] = (mid-l+1);
		tree[now<<1|1] = (r-mid);
	}
	else
	{
		tree[now<<1] = 0;
		tree[now<<1|1] = 0;
	}
	tag[now<<1] = tag[now];
	tag[now<<1|1] = tag[now];
	tag[now] = -1;
	return;
}
void updata(int now,int l,int r,int nl,int nr,int x)
{
	if(l <= nl && r >= nr)
	{
		tree[now] = (nr-nl+1) * x;
		tag[now] = x;
		return;
	}
	push_down(now,nl,nr);
	int mid = nl+nr>>1;
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,x);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,x);
	}
	push_up(now);
	return;
}
int query(int now,int l,int r,int nl,int nr)
{
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	push_down(now,nl,nr);
	int mid = nl+nr>>1;
	int ans = 0;
	if(l <= mid)
	{
		ans += query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		ans += query(now<<1|1,l,r,mid+1,nr);
	}
	return ans;
}
int plant(int pos,int x)
{
	if(x == 0) return pos;
	if(pos > n) return n;
	int l = pos,r = n;
	if((n-pos+1)-query(1,pos,n,1,n) < x)
	{
		while(l < r)
		{
			int mid = l+r+1>>1;
			if(query(1,mid,n,1,n) == (n-mid+1))
			{
				r = mid - 1;
			}
			else
			{
				l = mid;
			}
		}
	}
	else
	{
		while(l <= r)
		{
			int mid = l+r>>1;
			if((mid-pos+1)-query(1,pos,mid,1,n) >= x)
			{
				r = mid - 1;
			}
			else
			{
				l = mid + 1;
			}
		}
	}
	updata(1,pos,l,1,n,1);
	return l;
}
void solve()
{
	memset(tree,0,sizeof(tree));
	memset(tag,-1,sizeof(tag));
	scanf("%d %d",&n,&m);
	n;
	for(int i = 1;i <= m;i++)
	{
		int op;
		scanf("%d",&op);
		if(op == 1)
		{
			int pos,x;
			scanf("%d %d",&pos,&x);
			pos++;
			if(query(1,pos,n,1,n) == (n-pos+1))
			{
				cout << "Can not put any one.\n";
				continue;
			}
			int l = plant(pos,1);
			printf("%d %d\n",l-1,plant(l,x-1)-1);
		}
		else if(op == 2)
		{
			int l,r;
			scanf("%d %d",&l,&r);
			l++,r++;
			printf("%d\n",query(1,l,r,1,n));
			updata(1,l,r,1,n,0);
		}
	}
	printf("\n");
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
	return 0;
}

HDU - 4553 约会安排

题目大意:

有三种操作:屌丝兄弟来找你玩,且需要一段连续的长度K区间;

女神找你约会,且需要一段连续的长度K区间,由于女神比兄弟重要,你可以咕掉兄弟的时间去陪女神约会,但是优先看能否不咕,但是必须保证是一段连续区间,在原计划和兄弟的时间被女神占用后若原计划时间未结束则继续去陪兄弟;

你突然顿悟,准备学习,怒吼“I am the hope of chinese chengxuyuan!!”,然后删掉[l,r]时间内的所有计划,但是后面若有人找你,你会继续去玩

要求每次女神或兄弟找你的时候输出最早的一个时间点,要求这个时间开始有K个连续的时间是空空闲的,每次删掉计划后也要输出怒吼

思路:

很明显的我们要维护一段区间内的最长连续序列,还要推平区间

所以我们可以考虑用线段树来维护最长连续区间

最长连续区间和区间最大连续字段和有些一样,都需要维护区间最值和左右端点最值

若某个时间点被占用,我们可以将其置为0,否则置为1

此时的最长连续区间就是求最长的连续1的个数

题目中还有一个很重要的点就是女神的优先级高于兄弟,所以兄弟占用的时间可以被女神占用,但女神占用的时间不能被兄弟占用

所以考虑开两个线段树,一个兄弟树,一个女神树

女神树上的操作一定会给兄弟树也操作一遍,而兄弟树不会给女神树操作

push_up代码如下:

struct node{
	int lmax,rmax,smax,tag;
};
node tree1[200010*4];
node tree2[200010*4];
void push_up(int now,int l,int r)
{
	int mid = l+r>>1;
    //基本和区间最大连续字段和一样,理解了那个就会理解这个
	tree1[now].smax = max(tree1[now<<1].rmax + tree1[now<<1|1].lmax,
					  max(tree1[now<<1].smax,tree1[now<<1|1].smax));
	tree1[now].lmax = tree1[now<<1].lmax + (tree1[now<<1].lmax == (mid-l+1) ? tree1[now<<1|1].lmax : 0);
	tree1[now].rmax = tree1[now<<1|1].rmax + (tree1[now<<1|1].rmax == (r-mid) ? tree1[now<<1].rmax : 0);
	tree2[now].smax = max(tree2[now<<1].rmax + tree2[now<<1|1].lmax,
					  max(tree2[now<<1].smax,tree2[now<<1|1].smax));
	tree2[now].lmax = tree2[now<<1].lmax + (tree2[now<<1].lmax == (mid-l+1) ? tree2[now<<1|1].lmax : 0);
	tree2[now].rmax = tree2[now<<1|1].rmax + (tree2[now<<1|1].rmax == (r-mid) ? tree2[now<<1].rmax : 0);
	return;
}

这里注意懒标记下放,由于一个有标记的区间一定是被全覆盖的,所以此时lmax = rmax = smax

代码如下:

void push_down(int now,int l,int r)
{
	int mid = l+r>>1;
	if(tree1[now].tag != -1)
	{
		tree1[now<<1].smax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].lmax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].rmax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].tag = tree1[now].tag;
		tree1[now<<1|1].smax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].lmax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].rmax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].tag = tree1[now].tag;
		tree1[now].tag = -1;
	}
	if(tree2[now].tag != -1)
	{
		tree2[now<<1].smax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].lmax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].rmax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].tag = tree2[now].tag;
		tree2[now<<1|1].smax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].lmax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].rmax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].tag = tree2[now].tag;
		tree2[now].tag = -1;
	}
	return;
}

更新操作就不解释了,完全的模板

然后是查询操作,我们要找到一个连续的区间的左端点,所以先考虑答案会出现在哪里

  • 第一种可能,没有答案,也就是区间中的smax小于所需长度
  • 第二种可能,答案在该区间的左区间中,利用线段树的递归继续去左边找
  • 第三中可能,答案在左区间的rmaxl和右区间的lmax相加的这个区间中,那么答案就是左区间的右端点减去rmaxl+1的位置
  • 第四种可能,答案在右区间中,同样利用线段树递归继续求解

代码如下:

int query(int now,int l,int r,int op,int x)
{
    //这里判断是否无解,op为操作的树,1为女神树,0为兄弟树
	if(op)
	{
		if(tree2[now].smax < x) return -1;
	}
	else
	{
		if(tree1[now].smax < x) return -1;
	}
	if(l == r)
	{
		return l;
	}
	int mid = l+r>>1;
	push_down(now,l,r);
	if(op)
	{
        //左区间的最大值满足则去左区间找
		if(tree2[now<<1].smax >= x)
		{
			return query(now<<1,l,mid,op,x);
		}
        //中间区间满足则在中间区间中找
		else if(tree2[now<<1].rmax + tree2[now<<1|1].lmax >= x)
		{
			return mid - tree2[now<<1].rmax + 1;
		}
        //否则只能是右区间满足
		else
		{
			return query(now<<1|1,mid+1,r,op,x);
		}
	}
	else
	{
		if(tree1[now<<1].smax >= x)
		{
			return query(now<<1,l,mid,op,x);
		}
		else if(tree1[now<<1].rmax + tree1[now<<1|1].lmax >= x)
		{
			return mid - tree1[now<<1].rmax + 1;
		}
		else
		{
			return query(now<<1|1,mid+1,r,op,x);
		}
	}
}

最后看输入的操作,如果是兄弟直接操作兄弟树就可以,无解就是无解了,如果是女神则需要先操作兄弟树,有解就接在后面,无解就去找女神树,有解则同时覆盖兄弟树,否则无解

总体代码如下:

#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m,cnt;
struct node{
	int lmax,rmax,smax,tag;
};
node tree1[200010*4];
node tree2[200010*4];
void push_up(int now,int l,int r)
{
	int mid = l+r>>1;
	tree1[now].smax = max(tree1[now<<1].rmax + tree1[now<<1|1].lmax,
					  max(tree1[now<<1].smax,tree1[now<<1|1].smax));
	tree1[now].lmax = tree1[now<<1].lmax + (tree1[now<<1].lmax == (mid-l+1) ? tree1[now<<1|1].lmax : 0);
	tree1[now].rmax = tree1[now<<1|1].rmax + (tree1[now<<1|1].rmax == (r-mid) ? tree1[now<<1].rmax : 0);
	tree2[now].smax = max(tree2[now<<1].rmax + tree2[now<<1|1].lmax,
					  max(tree2[now<<1].smax,tree2[now<<1|1].smax));
	tree2[now].lmax = tree2[now<<1].lmax + (tree2[now<<1].lmax == (mid-l+1) ? tree2[now<<1|1].lmax : 0);
	tree2[now].rmax = tree2[now<<1|1].rmax + (tree2[now<<1|1].rmax == (r-mid) ? tree2[now<<1].rmax : 0);
	return;
}
void build(int now,int l,int r)
{
	tree1[now].smax = tree1[now].lmax = tree1[now].rmax = 1;
	tree2[now].smax = tree2[now].lmax = tree2[now].rmax = 1;
	tree1[now].tag = tree2[now].tag = -1;
	if(l == r) return;
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now,l,r);
	return;
}
void push_down(int now,int l,int r)
{
	int mid = l+r>>1;
	if(tree1[now].tag != -1)
	{
		tree1[now<<1].smax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].lmax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].rmax = (mid-l+1) * tree1[now].tag;
		tree1[now<<1].tag = tree1[now].tag;
		tree1[now<<1|1].smax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].lmax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].rmax = (r-mid) * tree1[now].tag;
		tree1[now<<1|1].tag = tree1[now].tag;
		tree1[now].tag = -1;
	}
	if(tree2[now].tag != -1)
	{
		tree2[now<<1].smax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].lmax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].rmax = (mid-l+1) * tree2[now].tag;
		tree2[now<<1].tag = tree2[now].tag;
		tree2[now<<1|1].smax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].lmax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].rmax = (r-mid) * tree2[now].tag;
		tree2[now<<1|1].tag = tree2[now].tag;
		tree2[now].tag = -1;
	}
	return;
}
void updata(int now,int l,int r,int nl,int nr,int op,int x)
{
	if(l <= nl && r >= nr)
	{
		if(op)
		{
			tree1[now].smax = tree2[now].smax = (nr-nl+1) * x;
			tree1[now].lmax = tree2[now].lmax = (nr-nl+1) * x;
			tree1[now].rmax = tree2[now].rmax = (nr-nl+1) * x;
			tree1[now].tag = tree2[now].tag = x;
		}
		else
		{
			tree1[now].smax = (nr-nl+1) * x;
			tree1[now].lmax = (nr-nl+1) * x;
			tree1[now].rmax = (nr-nl+1) * x;
			tree1[now].tag = x;
		}
		return;
	}
	push_down(now,nl,nr);
	int mid = nl+nr>>1;
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,op,x);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,op,x);
	}
	push_up(now,nl,nr);
}
int query(int now,int l,int r,int op,int x)
{
	if(op)
	{
		if(tree2[now].smax < x) return -1;
	}
	else
	{
		if(tree1[now].smax < x) return -1;
	}
	if(l == r)
	{
		return l;
	}
	int mid = l+r>>1;
	push_down(now,l,r);
	if(op)
	{
		if(tree2[now<<1].smax >= x)
		{
			return query(now<<1,l,mid,op,x);
		}
		else if(tree2[now<<1].rmax + tree2[now<<1|1].lmax >= x)
		{
			return mid - tree2[now<<1].rmax + 1;
		}
		else
		{
			return query(now<<1|1,mid+1,r,op,x);
		}
	}
	else
	{
		if(tree1[now<<1].smax >= x)
		{
			return query(now<<1,l,mid,op,x);
		}
		else if(tree1[now<<1].rmax + tree1[now<<1|1].lmax >= x)
		{
			return mid - tree1[now<<1].rmax + 1;
		}
		else
		{
			return query(now<<1|1,mid+1,r,op,x);
		}
	}
}
void solve()
{
	printf("Case %d:\n",++cnt);
	int n,m;
	scanf("%d %d",&n,&m);
	build(1,1,n);
	for(int i = 1;i <= m;i++)
	{
		char op[10];
		scanf("%s",op);
		if(op[0] == 'D')
		{
			int x;
			scanf("%d",&x);
			int pos = query(1,1,n,0,x);
			if(pos == -1 || x > n)
			{
				printf("fly with yourself\n");
			}
			else
			{
				updata(1,pos,pos+x-1,1,n,0,0);
				printf("%d,let's fly\n",pos);
			}
		}
		else if(op[0] == 'N')
		{
			int x;
			scanf("%d",&x);
			int pos = query(1,1,n,0,x);
			if(pos == -1 || x > n)
			{
				pos = query(1,1,n,1,x);
				if(pos == -1 || x > n)
				{
					printf("wait for me\n");
				}
				else
				{
					updata(1,pos,pos+x-1,1,n,1,0);
					printf("%d,don't put my gezi\n",pos);
				}
			}
			else
			{
				updata(1,pos,pos+x-1,1,n,1,0);
				printf("%d,don't put my gezi\n",pos);
			}
		}
		else
		{
			int l,r;
			scanf("%d %d",&l,&r);
			updata(1,l,r,1,n,1,1);
			printf("I am the hope of chinese chengxuyuan!!\n");
		}
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
	return 0;
}

洛谷P2824 [HEOI2016/TJOI2016] 排序

题目大意:

给出一个长度为n的1~n的排列,有m次操作,每次会对一个区间[l,r]进行一次升序/降序排列,求最最操作后的第p位数是多少

思路:

如果单纯的暴力每次都对一个区间排序的话那么肯定会超时,可以猜测到想要通过此题肯定是要优化掉排序这个复杂度的

  • 第一个关键点:给出的数组是一个1~n的排列,也就是说答案肯定在1~n中
  • 第二个关键点:每次只会升序/降序排序,也就是大的在前面和小的在前面的区别

考虑如何操作能让一个区间快速完成这样的降序/升序排列,既然我们不能精确的排序,那我们就要像是否有办法对一个区间进行一个大概的排序

假设以一个数x为标准,将这个区间内所以大于等于x的记为1,小于x的记为0

进行一次升序排序后这个区间是不是就变成了前面全部为0后面全部为1的情况,反之降序排序同理,我们可以通过不断地调整我们猜测的这个数x来让最后的结果变得准确

考虑在1到n上二分这个x,这里进行一个二分的正确性的简单证明:

首先我们知道m次操作后的结果是不变的,用上面的01排序后数组中只会出现0和1

如果最后p位置上的数字是1那就说明这个数大于等于x,否则就小于x,满足了单调性

如果能找到一个数x满足以x为标准时01排序后p位置的值为1且以x-1为标准时01排序后p位置的值为0,也就是说x>=a[p]且x-1<a[p],能得出x=a[p]

思路有了那么我们考虑如果进行01排序这个操作

既然只有01这两个数,那么升序排列也就是把所有的1全放在后面的这个操作可以分解为求区间长度的区间1的个数之差,然后让前面的这个区间全为0,后面的区间全为1即可,也就是线段树的区间赋值操作

有了这个思想后代码其实很简单,就不一步步解释了,直接看总代码:

#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m,k,ans;
int b[100010];
int a[100010];
int tag[100010*4];
int tree[100010*4];
struct node{
	int l,r,op;
}q[100010];

void push_up(int now)
{
	tree[now] = tree[now<<1] + tree[now<<1|1];
}
void change(int now,int l,int r,int num)
{
	tree[now] = (r-l+1) * num;
	tag[now] = num;
}
void push_down(int now,int l,int r)
{
	if(tag[now] >= 0)
	{
		int mid = l+r>>1;
		change(now<<1,l,mid,tag[now]);
		change(now<<1|1,mid+1,r,tag[now]);
		tag[now] = -1;
	}
}
void build(int now,int l,int r)
{
	if(l == r)
	{
		tree[now] = b[l];
		return;
	}
	int mid = l+r>>1;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	push_up(now);
}
void updata(int now,int l,int r,int nl,int nr,int num)
{
	if(nl > r || nr < l) return;
	if(l <= nl && r >= nr)
	{
		tree[now] = (nr-nl+1) * num;
		tag[now] = num;
		return;
	}
	int mid = nl+nr>>1;
	push_down(now,nl,nr);
	if(l <= mid)
	{
		updata(now<<1,l,r,nl,mid,num);
	}
	if(r > mid)
	{
		updata(now<<1|1,l,r,mid+1,nr,num);
	}
	push_up(now);
}
int query(int now,int l,int r,int nl,int nr)
{
	if(nl > r || nr < l) return 0;
	if(l <= nl && r >= nr)
	{
		return tree[now];
	}
	push_down(now,nl,nr);
	int mid = nl+nr>>1;
	int ans = 0;
	
	if(l <= mid)
	{
		ans += query(now<<1,l,r,nl,mid);
	}
	if(r > mid)
	{
		ans += query(now<<1|1,l,r,mid+1,nr);
	}
	return ans;
}
void check(int x)
{
	for(int i = 1;i <= n;i++)
	{
		b[i] = a[i] >= x;
	}
	memset(tree,0,sizeof(tree));
	memset(tag,-1,sizeof(tag));
	build(1,1,n);
	for(int i = 1;i <= m;i++)
	{
		int op = q[i].op;
		int l = q[i].l,r = q[i].r;
		int len = query(1,l,r,1,n);
		if(op == 0)
		{
			updata(1,l,r-len,1,n,0);
			updata(1,r-len+1,r,1,n,1);
		}
		else if(op == 1)
		{
			updata(1,l+len,r,1,n,0);
			updata(1,l,l+len-1,1,n,1);
		}
	}
}
int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> m;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}
	for(int i = 1;i <= m;i++)
	{
		cin >> q[i].op >> q[i].l >> q[i].r;
	}
	cin >> k;
	int l = 1,r = n;
	while(l <= r)
	{
		int mid = l+r>>1;
		check(mid);
		if(query(1,k,k,1,n))
		{
			ans = mid;
			l = mid + 1;
		}
		else
		{
			r = mid - 1;
		}
	}
	cout << ans;
	return 0;
}

这里二分之后要判断一下第p位是否为1(也就是是否大于等于x)然后再进行左右指针的加减操作

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值