Fork me on GitHub

hdu 6406 && 2021年牛客多校第七场B题 xay loves monotonicity(线段树区间合并)

namo,第二篇博客记录下以前没见过的奇妙区间合并。
首先是杭电6406。题意如下,给定长为 n 的 a 数组,再给出 m 个询问,每次询问会给 pos 和 val 两个数,要求回答,将原数组的 pos 位置改成 val 后,从左开始取数,首先 a[1] 必取,之后比 a[1] 大的第一个数必取,设为 x ,之后再取比 x 大的第一个数... 问最长能取的序列长度。(每次修改互不影响,n m 1e5)样例的输入输出题面有解释,在此不过多赘述。
下面来解释做法,首先如果直接维护贡献的话,在进行 pushup 的时候,考虑子树 u 的贡献组成,左子树 u<<1 一定是有贡献的,(因为答案序列是从 1 位置往右遍历的来)。而右子树区间是需要重新calc计算的,接下来考虑右子树 u<<1|1 的贡献。假设左子树的最大值为 lmx ,右子树的最大值为 rmx ,右子树的左子树(后面叫做右左子树)最大值为 rlmx ,右子树的右子树(后面叫做右右子树)最大值为 rrmx。
if rightmx <= leftmx,那么此时右子树的贡献为0。
else if rlmx >= rrmx的话,此时右右子树贡献也为0
else if rlmx < rrmx的话,此时右子树的左子树和右子树部分都有贡献产生,此时我们只需要加上右右子树的贡献并递归查询右左子树贡献即可,这里注意,右右子树的贡献并不是tr[(u<<1|1)<<1|1].ans,而是tr[u<<1|1].ans - tr[(u<<1|1)<<1].ans,为什么呢,首先还是要清楚我们每个子树区间维护的ans含义是从当前区间左端点开始所形成最长上升子序列的答案,tr[u<<1].ans仅仅是针对右子树区间来说。
接下来将u = u<<1|1,偏移到右子树区间来说,如果 rlmx < rrmx的话,此时左子树的贡献为calc(u<<1,lmx),右子树区间为tr[u].ans - tr[u<<1].ans;到这里大概就比较明了了,所以我们需要维护一个max和区间ans。
下面给出代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int IOS = []() {ios::sync_with_stdio(0); std::cin.tie(nullptr); std::cout.tie(nullptr); return 0; }();
const int N = 2e6+10,inf = 1e9;
int a[N];
struct node{
	int l,r;
	int mx,ans; //最大值 以当前区间左端点为起点,"LIS"的长度答案 
}tr[N*4];
int calc(int u,int l,int r,int mx)
{
	if(l==r) return tr[u].mx > mx;  //叶子结点直接算
	if(tr[u].mx <= mx) return 0;    //如果当前右子树max小于等于左子树max,右子树没贡献
	int mid = l + r >>1;
	if(tr[u<<1].mx <= mx) return calc(u<<1|1,mid+1,r,mx);  //如果右左子树max小于左子树max,直接递归算右右子树贡献
	else return tr[u].ans - tr[u<<1].ans + calc(u<<1,l,mid,mx); //否则加上右右子树的贡献去递归算右左子树的贡献
}
void build(int u,int l,int r)
{
	if(l==r)
	{
		tr[u].ans = 1;  // 叶子节点的初始化应该不用多说
		tr[u].mx = a[l];
		return;
	}
	int mid = l + r >>1;
	build(u<<1,l,mid); build(u<<1|1,mid+1,r);
	//max直接维护,左儿子的贡献直接加,因为此时是以左区间的max为起点选数,所以右儿子的贡献需要重新计算
	tr[u].mx = max(tr[u<<1].mx,tr[u<<1|1].mx);
	tr[u].ans = tr[u<<1].ans + calc(u<<1|1,mid+1,r,tr[u<<1].mx);
}
void update(int u,int l,int r,int x,int k)  // 正常的单点修改
{
	if(l==r)
	{
		tr[u].ans = 1; 
		tr[u].mx = k;
		return; 
	} 
	int mid = l + r>>1;
	if(x<=mid) update(u<<1,l,mid,x,k);
	else update(u<<1|1,mid+1,r,x,k);
	tr[u].mx = max(tr[u<<1].mx,tr[u<<1|1].mx);
	tr[u].ans = tr[u<<1].ans + calc(u<<1|1,mid+1,r,tr[u<<1].mx);
}
void solve()
{
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	while(m--)
	{
		int l,r; cin>>l>>r;
		int t = a[l];  //记录用来恢复修改
		update(1,1,n,l,r);
		cout<<tr[1].ans<<endl;
		update(1,1,n,l,t);
	}
} 
signed main()
{
	int tt = 1;
	cin>>tt;
	while(tt--)
	{
		solve();
	}
}

下面是牛客多校的该题进阶版。
题意:给出一个长度为 n 的数字序列 a 和 01 序列 b ,需要执行 m 次操作,每次操作分为如下三种类型:
1 x y:修改 a [x] = y;
2 l r:区间 [l,r] 内的 b 置反 (b[i] = (b[i] + 1) mod 2)
3 L R:输出区间 [L,R] 的"贡献"
本题的贡献为,第 L 个数必选,之后选的规则和上题一样,假设所选最长序列在a数组的下标为pos1(pos1==L),pos2,pos3…… 再将数组 b 中相应的01序列取出,贡献即为此时01序列的 和前一个相邻数不同的个数。
和上题类似,现在考虑如果维护该序列映射到数组 b 产生的贡献,容易发现只需要再额外维护一个区间的最大值所在处的 b 序列为 1 还是 0 即可,pushup 维护的过程类似遍历,但此时需要对 mx 和 pre(前一位的最大值和前一位是 1 还是 0) 进行实时更新,这一步传个引用即可。操作一就是普通的单点修改,操作二维护个tag即可。
代码如下:

点击查看代码
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,abm,mmx,avx,avx2,popcnt,tune=native")
#pragma GCC target ("avx2,fma")
#pragma GCC optimize ("O3")
#pragma GCC optimize ("unroll-loops")
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int IOS = []() {ios::sync_with_stdio(0); std::cin.tie(nullptr); std::cout.tie(nullptr); return 0; }();
const int N = 2e5+10;
int a[N],b[N];
struct node{
	int l,r;
	int mx,cnt; // 区间最值mx 以左端点为起点的最长不下降子序列
	int bnum,tag; // 区间最值位置的b值 懒标记   (很奇怪,这里用bool类型甚至会慢很多
}tr[N*4];
void pushdown(int u)
{
	if(tr[u].tag)
	{
		tr[u].tag = 0;
		tr[u<<1].bnum ^= 1; tr[u<<1].tag ^= 1;
		tr[u<<1|1].bnum ^= 1; tr[u<<1|1].tag ^= 1;
	}
}
int calc(int u,int &mx,int &pre)  // 在子树 u 中以 mx 为起点的贡献, pre 为 mx 位置的 b 的值
{
	if(tr[u].mx < mx) return 0;
	if(tr[u].l == tr[u].r)
	{
		if(tr[u].mx >= mx)  // 满足不下降
		{
			int ans = (tr[u].bnum != pre);  // 贡献需要和前一个b不一样
			mx = tr[u].mx,pre = tr[u].bnum; // 修改mx和pre
			return ans;
		}
		else return 0;
	}
	pushdown(u);
	if(tr[u<<1].mx < mx) return calc(u<<1|1,mx,pre);
	else
	{
		int ans = calc(u<<1,mx,pre) + tr[u].cnt - tr[u<<1].cnt;
		mx = tr[u].mx,pre = tr[u].bnum;  // 这句话很重要,注意递归完需要修改!(感觉会有点容易忘写
		return ans;
	}	
}
void pushup(int u)
{
	if(tr[u<<1].mx>tr[u<<1|1].mx) tr[u].bnum = tr[u<<1].bnum;
	else tr[u].bnum = tr[u<<1|1].bnum;
	tr[u].mx = max(tr[u<<1].mx,tr[u<<1|1].mx);
	int mx = tr[u<<1].mx,pre = tr[u<<1].bnum;
	tr[u].cnt = tr[u<<1].cnt + calc(u<<1|1,mx,pre);
}
void build(int u,int l,int r)
{
	tr[u] = {l,r};
	if(l==r)
	{
		tr[u].mx = a[l];
		tr[u].bnum = b[l];  
		return;
	}
	int mid = l + r >>1;
	build(u<<1,l,mid); build(u<<1|1,mid+1,r);
	pushup(u);
}
void update1(int u,int pos,int val)
{
	if(tr[u].l==tr[u].r) 
	{
		tr[u].mx = val; return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >>1;
	if(pos<=mid) update1(u<<1,pos,val);
	else update1(u<<1|1,pos,val);
	pushup(u);
}
void update2(int u,int l,int r)
{
	if(tr[u].l>=l&&tr[u].r<=r) 
	{
		tr[u].bnum ^= 1;
		tr[u].tag ^= 1;
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >>1;
	if(l<=mid) update2(u<<1,l,r);
	if(r>mid)  update2(u<<1|1,l,r);
	pushup(u);
}
int query(int u,int l,int r,int &mx,int &pre)
{
	if(tr[u].l>r||tr[u].r<l) return 0;
	if(tr[u].l>=l&&tr[u].r<=r) 
	{
		if(tr[u].mx >= mx) return calc(u,mx,pre);
		else return 0;
	}
	pushdown(u);
	return query(u<<1,l,r,mx,pre)+query(u<<1|1,l,r,mx,pre);
}
int query1(int u,int pos)
{
	if(tr[u].l == tr[u].r) return tr[u].mx;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >>1;
	if(pos<=mid) return query1(u<<1,pos);
	else return query1(u<<1|1,pos);
}
int query2(int u,int pos)
{
	if(tr[u].l == tr[u].r) return tr[u].bnum;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >>1;
	if(pos<=mid) return query2(u<<1,pos);
	else return query2(u<<1|1,pos);
}
void solve()
{
	int n,m; cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	build(1,1,n);
	cin>>m;
	while(m--)
	{
		int op; cin>>op;
		int l,r; cin>>l>>r;
		if(op==1) update1(1,l,r);
		else if(op==2) update2(1,l,r);
		else
		{
			int mx = query1(1,l);
			int pre = query2(1,l);  
			cout<<query(1,l,r,mx,pre)<<endl;
		}
	}
} 
signed main()
{
	int tt = 1;
//	cin>>tt;
	while(tt--)
	{
		solve();
	}
}
posted @ 2021-11-16 22:14  LeiLeiKunLe  阅读(71)  评论(1)    收藏  举报