2020-2021年度第二届全国大学生算法设计与编程挑战赛 (春季赛)- 天才的操作(线段树+主席树+树上倍增)

Posted Frozen_Guardian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2020-2021年度第二届全国大学生算法设计与编程挑战赛 (春季赛)- 天才的操作(线段树+主席树+树上倍增)相关的知识,希望对你有一定的参考价值。

在这里插入图片描述
题目链接:点击查看

题目分析:刚看到这个题目的时候,口胡了一个假算法,觉得对于每次询问的操作 [ l , r ] [l,r] [l,r] ,只需要找到指令集区间 [ l , r ] [l,r] [l,r] 内覆盖到点 k k k 的最大并集,然后求出这段区间中 a a a 数组的最大值就是答案了,这样一来直接用线段树维护一下数组 a a a,再用主席树维护一下区间交集即可

但后来转念一想,覆盖的区间还有时序问题,也就是两个区间执行的先后顺序不同,会造成不一样的结果,到此为止这个题也就卡住了

今天看了题解和杨大佬讨论了一下就豁然开朗了,其实卡住的部分就差预处理出一个树上倍增了

因为本题中的指令集操作,本质上是将一个区间的数值都赋值为该区间的最大值。所以我们的最终目标仍然是需要找到包含点 k k k 的区间向左向右分别可以扩展到什么位置,记为 [ l l , r r ] [ll,rr] [ll,rr],那么答案就是数组 a a a [ l l , r r ] [ll,rr] [ll,rr] 中的最大值了,下面以向右扩展为例来讲解,向左扩展的话只需要类比过去就好啦

将指令集视为一棵树,对于第 i i i 个指令来说,其代表的区间为 [ l i , r i ] [l_i,r_i] [li,ri],因为我们需要尽可能向右扩展,所以我们查找一下点 r i r_i ri 最后一次被哪个指令集所覆盖,记为 j j j,那么此时就会产生一条 j − > i j->i j>i 的有向边,如此建图的话,最后就会形成一棵以点 0 0 0 为根节点的树(其实严格意义上来说,是以点 0 0 0 为根节点的一棵森林)

而且不难发现,假设 j j j i i i 有一条边,且 j j j i i i 的父节点,那么满足两个性质:

  1. j < i j<i j<i
  2. r j > = r i r_j>=r_i rj>=ri

所以对于给定的点 k k k ,我们只需要找到其最多可以扩展到的位置就是上文中需要求的 r r rr rr

而不断向上跳 f a fa fa 的过程也可以利用倍增快速实现

如果每次询问的都是 [ 1 , m ] [1,m] [1,m] 中的指令集,那么问题就已经解决了所以现在只剩下了一个问题,那就是指令集限制在 [ l , r ] [l,r] [l,r] 内该如何实现呢?

注意到上面提到的,建出来的树的性质一,即从根节点到任意节点的编号都是单调递增的,所以我们可以从第 r r r 个指令集不断向上跳,跳到深度最低,且指令集的编号仍然大于等于 l l l 的那个祖先为止

所以现在的目标就转换为了,如何建出上文中的那棵树呢?其实只需要实现下面两个操作即可:

  1. 将某个版本的的某个区间覆盖成指定颜色
  2. 查询某个版本指定位置的颜色的最大值

因为涉及到了历史版本和区间问题,所以用主席树来维护就好啦

需要注意的是,区间修改要用到标记永久化,并且对于某段区间的修改,保守估计会有 3 l o g n 3logn 3logn 个节点会被修改,所以主席树的数组大小需要注意一下

代码:

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast","inline","-ffast-math")
// #pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<list>
#include<unordered_map>
#define lowbit(x) x&-x
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
template<typename T>
inline void read(T &x)
{
	T f=1;x=0;
	char ch=getchar();
	while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	x*=f;
}
template<typename T>
inline void write(T x)
{
	if(x<0){x=~(x-1);putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
const int inf=0x3f3f3f3f;
const int N=1e5+100;
int n,L[N][25],R[N][25],ql[N],qr[N];
struct Seg {
	struct Node {
		int l,r,mmax;
	}tree[N<<2];
	void pushup(int k) {
		tree[k].mmax=max(tree[k<<1].mmax,tree[k<<1|1].mmax);
	}
	void build(int k,int l,int r) {
		tree[k]={l,r,0};
		if(l==r) {
			read(tree[k].mmax);
			return;
		}
		int mid=(l+r)>>1;
		build(k<<1,l,mid);
		build(k<<1|1,mid+1,r);
		pushup(k);
	}
	void update(int k,int pos,int val) {
		if(tree[k].l==tree[k].r) {
			tree[k].mmax=val;
			return;
		}
		int mid=(tree[k].l+tree[k].r)>>1;
		if(pos<=mid) {
			update(k<<1,pos,val);
		} else {
			update(k<<1|1,pos,val);
		}
		pushup(k);
	}
	int query(int k,int l,int r) {
		if(tree[k].l>r||tree[k].r<l) {
			return 0;
		}
		if(tree[k].l>=l&&tree[k].r<=r) {
			return tree[k].mmax;
		}
		return max(query(k<<1,l,r),query(k<<1|1,l,r));
	}
}SEG;
struct CMT {
	struct Node {
		int l,r,lazy;
	}tree[N*60];
	int root[N],tot;
	int newnode() {
		tot++;
		tree[tot].l=tree[tot].r=tree[tot].lazy=0;
		return tot;
	}
	void update(int &k,int l,int r,int L,int R,int val) {//[l,r]:target [L,R]:cur
		if(L>r||R<l) {
			return;
		}
		int nk=newnode();
		tree[nk]=tree[k];
		k=nk;
		if(L>=l&&R<=r) {
			tree[k].lazy=val;
			return;
		}
		int mid=(L+R)>>1;
		update(tree[k].l,l,r,L,mid,val);
		update(tree[k].r,l,r,mid+1,R,val);
	}
	int query(int k,int L,int R,int pos,int val) {//[L,R]:cur
		val=max(val,tree[k].lazy);
		if(L==R) {
			return val;
		}
		int mid=(L+R)>>1;
		if(pos<=mid) {
			return query(tree[k].l,L,mid,pos,val);
		} else {
			return query(tree[k].r,mid+1,R,pos,val);
		}
	}
	void update(int l,int r,int val) {//将[l,r]染色成val
		update(root[val],l,r,1,n,val);
	}
	int query(int k,int pos) {//查询第k个版本点pos的颜色
		return query(root[k],1,n,pos,0);
	}
}CMT;
int main()
{
#ifndef ONLINE_JUDGE
//	freopen("data.in.txt","r",stdin);
//	freopen("data.out.txt","w",stdout);
#endif
//	ios::sync_with_stdio(false);
	int m,q;
	read(n),read(m),read(q);
	SEG.build(1,1,n);
	for(int i=1;i<=m;i++) {
		read(ql[i]),read(qr[i]);
		L[i][0]=CMT.query(i-1,ql[i]);
		R[i][0]=CMT.query(i-1,qr[i]);
		CMT.root[i]=CMT.root[i-1];
		CMT.update(ql[i],qr[i],i);
		for(int j=1;j<=20;j++) {
			L[i][j]=L[L[i][j-1]][j-1];
			R[i][j]=R[R[i][j-1]][j-1];
		}
	}
	while(q--) {
		int op;
		read(op);
		if(op==1) {
			int x,y;
			read(x),read(y);
			SEG.update(1,x,y);
		} else if(op==2) {
			int l,r,k;
			read(l),read(r),read(k);
			int ll=CMT.query(r,k);
			int rr=ll;
			for(int i=20;i>=0;i--) {
				if(R[rr][i

以上是关于2020-2021年度第二届全国大学生算法设计与编程挑战赛 (春季赛)- 天才的操作(线段树+主席树+树上倍增)的主要内容,如果未能解决你的问题,请参考以下文章

2020-2021年度第二届全国大学生算法设计与编程挑战赛(冬季赛)——正式赛

2020-2021年度第二届全国大学生算法设计与编程挑战赛 (春季赛)- 天才的操作(线段树+主席树+树上倍增)

个人赛组2021-2022年度第三届全国大学生算法设计与编程挑战赛(秋季赛)-正式赛

个人赛组2021-2022年度第三届全国大学生算法设计与编程挑战赛(秋季赛)——热身赛

2021-2022年度第三届全国大学生算法设计与编程挑战赛(冬季赛)-正式赛 部分题解

2021-2022年度第三届全国大学生算法设计与编程挑战赛(冬季赛正式赛) 部分题题解