我只负责 D、F题解。

比赛链接-2024年冬第八届河北工业大学程序设计校赛

D Serious Dedication II(终极奉献 II)

很麻烦的一道题,为了卡掉暴力搞得非常严肃。二维前缀和的题目大概以后不会出了……

访问每个位置的“菱形”情况,需要把单次访问操作的时间压下来,尽量用运算个数固定的访问方法。

本题解二维前缀和的部分可见于终极奉献 I,比赛链接

对齐解法

通过 bfs 扩散,将输入的中心点坐标连续扩散 次就得到了已有耕地的完整耕地图。

扩散图

对齐法

C/C++ 代码:

#include<stdio.h>
int a[2500][2500],b1[2500][5000],b2[2500][5000];
int qx[2499*2499],qy[2499*2499],ql,qr;
const int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int ans[1998001+1];
int main()
{
	int n,k,l;scanf("%d%d%d",&n,&k,&l);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",qx+i,qy+i);
		a[qx[i]][qy[i]]=1;
	}
	qr=n-1;//队列有效下标闭区间[ql,qr]
	for(int _=0;_<k;_++)//扩散k次
	{
		int tqr=qr;//标记现在qr位置
		while(ql<=tqr)
		{
			int x=qx[ql],y=qy[ql];ql++;
			for(int f=0;f<4;f++)
			{
				int xx=x+dx[f],yy=y+dy[f];
				if(a[xx][yy]==0)
				{
					a[xx][yy]=1;
					++qr;qx[qr]=xx;qy[qr]=yy;
				}
			}
		}
	}
	for(int i=1;i<=l;i++)for(int j=1;j<=l;j++)
		a[i][j]=!a[i][j]+a[i][j-1];
		//一维行前缀和
	for(int x=1;x<=l;x++)for(int y=1;y<=l;y++)
		b1[x][y-x+l]=b2[x][x+y]=a[x][y];
		//应用坐标变换
		//u=x
		//v=y-x+l和v=x+y
	for(int i=1;i<=l;i++)for(int j=1;j<=2*l;j++)
		b1[i][j]+=b1[i-1][j],b2[i][j]+=b2[i-1][j];
		//一维列前缀和
	for(int x0=k+1;x0+k<=l;x0++)
		for(int y0=k+1;y0+k<=l;y0++)
		{
			int sum=0;
			sum+=b1[x0][(y0+k)-x0+l]-b1[x0-k-1][(y0+k)-x0+l];
			sum-=b1[x0+k][(y0-1)-(x0+k)+l]-b1[x0-1][(y0-1)-(x0+k)+l];
			sum+=b2[x0+k][(x0+k)+y0]-b2[x0-1][(x0+k)+y0];
			sum-=b2[x0][x0+(y0-k-1)]-b2[x0-k-1][x0+(y0-k-1)];
			sum-=a[x0][y0+k]-a[x0][y0-k-1];
			//当然你也可以当初加的时候就省去
			//a[x0][y0+k]-a[x0][y0-k-1];这块
			ans[sum]++;
		}
	int q;scanf("%d",&q);
	while(q--)
	{
		int a;scanf("%d",&a);
		printf("%d\n",ans[a]);
	}
	return 0;
}

旋转做法(不分奇偶)

考虑将菱形旋转 看看

旋转看看

利用坐标公式

可以将原有的“菱形”格点坐标集合化为“正方形”坐标集合。

考虑到这种变换会出现负数,给 加一个截距即可。

旋转奇偶合并处理

将原本黄色的格子旋转后得到的蓝色格子是有无相间的,根据计算可知这些蓝色格子两个坐标之和要不是奇数,要不就是偶数,处理的时候把空白位置空出来就行。

C/C++ 代码:

#include<stdio.h>
int a[5001][5001];//空间给够就是硬气
int ans[1998001+1];
int main()
{
	int n,k,l;scanf("%d%d%d",&n,&k,&l);
	for(int i=0;i<n;i++)
	{
		int x0,y0;scanf("%d%d",&x0,&y0);
		int x1=x0+y0,y1=y0-x0+l;
		a[x1-k][y1-k]++;
		a[x1-k][y1+k+1]--;
		a[x1+k+1][y1-k]--;
		a[x1+k+1][y1+k+1]++;
		//储存为差分的形式
	}
	for(int i=1;i<=2*l;i++)for(int j=1;j<=2*l;j++)
		a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
		//做二维前缀和
	for(int u=1;u<=2*l;u++)for(int v=1;v<=2*l;v++)
	{
		//u+v+l=(x+y)+(y-x+l)+l=2*(y+l) 是偶数
		//只有u+v+l是偶数是有效格,奇数的格一律设置成0
		if(u+v+l&1)a[u][v]=0;
		else a[u][v]=!a[u][v];
		//如果某有效格被遍历过1次及以上,那么记为0,否则是1
		//0和1分别表示该格上的空耕地/不被包含的格子数
		//显然其值只能是0或1
	}
	for(int i=1;i<=2*l;i++)for(int j=1;j<=2*l;j++)
		a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
		//做二维前缀和,方便后面算二维切片的元素总和
	for(int x=k+1;x+k<=l;x++)for(int y=k+1;y+k<=l;y++)
	{
		int x1=x+y,y1=y-x+l;
		int res=a[x1+k][y1+k]-a[x1-k-1][y1+k]-a[x1+k][y1-k-1]+a[x1-k-1][y1-k-1];
		ans[res]++;//预处理出来所有可能的方案
	}
	int q;scanf("%d",&q);
	while(q--)
	{
		int a;scanf("%d",&a);
		printf("%d\n",ans[a]);
	}
	return 0;
}

旋转解法(分奇偶,比较精致,仅供参考)

最初的想法,把空间利用到了极致,但是本题空间给得很大,不推荐这么写。

旋转看看

我们观察到 只能套在 外面, 只能套在 外面,遂将格点分奇偶讨论。

分奇偶旋转示意:

旋转示意

返回到原图中看看: 坐标与范围示意

观察到:设旋转前格点的坐标为 ,那么 相同的格点在一条斜线上, 相同的格点在一条斜线上,两种斜线正交。根据我们坐标转化的经验,那么设旋转后的坐标为 ,则可以有

由于我们先前分了奇偶两组(如果不分奇偶似乎也不是不可以做,这种操作似乎更“脑淤血”了),所以这种坐标也是分奇偶的,意味着相邻有意义的格点一维坐标相差 ,让它们算完再除以 即可。

我们发现这个 有可能是个负数,再加上一个截距让起始坐标 对齐后就完美了:

注意这套坐标转换公式要套两组不同的奇偶坐标。最后通过找关键格点对齐截距,当然数组开得够大,坐标也可以不从 开始。

对齐截距

对齐坐标后就坐标就都在 上了,非常方便我们使用二维前缀和做后续的操作。

C/C++ 代码:

#include<stdio.h>
int ec[1502][1502],oc[1502][1502];
//even coordinate & odd coordinate
//偶数坐标 和 奇数坐标 两套坐标
int ebu,ebv,obu,obv;
//even/odd (coordinates) base
//坐标变换的两套变换截距,用这个截距对齐坐标位置
//u=(x+y)/2+bu
//v=(y-x)/2+bv
//分出来两套坐标截距的做法比较精致,也可以不这么做
int ans[2002001+1];
int isodd(int x,int y){return (x+y)&1;}
int d(int x){return (x-(x&1))/2;}//d是⌊x/2⌋函数
//C/C++ 的整数除法是向零取整
//但是此处我们要求严格向下取整
void add(int x,int y,int val)
{
	if(isodd(x,y))oc[d(x+y)+obu][d(y-x)+obv]+=val;
	else ec[d(x+y)+ebu][d(y-x)+ebv]+=val;
}
int query(int x,int y)
{
	if(isodd(x,y))return oc[d(x+y)+obu][d(y-x)+obv];
	else return ec[d(x+y)+ebu][d(y-x)+ebv];
}
int main()
{
	int n,k,l;scanf("%d%d%d",&n,&k,&l);
	//用 (1,k+1)/(2,k+1) 确定两套新坐标下的第一行 ⌊(x+y)/2⌋=1
	if(isodd(1,k+1))obu=1-d(1+(k+1)),ebu=1-d(2+(k+1));
	else ebu=1-d(1+(k+1)),obu=1-d(2+(k+1));
	//用 (l-k,1)/(l-k,2) 确定两套新坐标下的第一列 ⌊(y-x)/2⌋=1
	if(isodd(l-k,1))obv=1-d(1-(l-k)),ebv=1-d(2-(l-k));
	else ebv=1-d(1-(l-k)),obv=1-d(2-(l-k));
	for(int i=0;i<n;i++)
	{
		int x0,y0;scanf("%d%d",&x0,&y0);
		add(x0,y0-k,1);add(x0+k+1,y0+1,-1);
		add(x0-k-1,y0+1,-1);add(x0,y0+k+2,1);
		add(x0,y0-k+1,1);add(x0+k,y0+1,-1);
		add(x0-k,y0+1,-1);add(x0,y0+k+1,1);
		//储存为差分的形式
	}
	for(int i=1;i<=l-k;i++)for(int j=1;j<=l-k;j++)
	{
		ec[i][j]+=ec[i-1][j]+ec[i][j-1]-ec[i-1][j-1];
		oc[i][j]+=oc[i-1][j]+oc[i][j-1]-oc[i-1][j-1];
		//做二维前缀和
	}
	for(int i=1;i<=l-k;i++)for(int j=1;j<=l-k;j++)
	{
		ec[i][j]=!(ec[i][j]);
		oc[i][j]=!(oc[i][j]);
		//如果某格被遍历过1次及以上,那么记为0,否则是1
		//0和1分别表示该格上的空耕地/不被包含的格子数
		//显然其值只能是0或1
	}
	for(int i=1;i<=l-k;i++)for(int j=1;j<=l-k;j++)
	{
		ec[i][j]+=ec[i-1][j]+ec[i][j-1]-ec[i-1][j-1];
		oc[i][j]+=oc[i-1][j]+oc[i][j-1]-oc[i-1][j-1];
		//做二维前缀和,方便后面算二维切片的元素总和
	}
	for(int i=k+1;i<=l-k;i++)for(int j=k+1;j<=l-k;j++)
	{
		int res=0;
		res+=query(i,j+k)-query(i+k+1,j-1)-query(i-k-1,j-1)+query(i,j-k-2);
		res+=query(i,j+k-1)-query(i+k,j-1)-query(i-k,j-1)+query(i,j-k-1);
		ans[res]++;
		//预处理出来所有可能的方案
	}
	int q;scanf("%d",&q);
	for(int i=0;i<q;i++)
	{
		int a;scanf("%d",&a);
		printf("%d\n",ans[a]);
	}
	return 0;
}

F Rheanna's General List

数据结构题,考虑好硬上就行,细节有点多,有多种方法:

线段树

线段树维护,记录深度信息。

一棵线段树 s1 维护区间最小值和最小值的个数,顺便适用树上结点表示的段的最小值为 的最小值数量求得最外层表上的位置信息;另一个线段树 s2 维护区间最大值即可。

为什么要分两个树?因为 MERGE 时,一个树不能既让表首深度保持 1,又体现出来表首的深度加一了。

MERGE 只需要给 s1 [L+1,R] 数值加一,给 s2 [L,R] 数值加一。

然后记录每次修改的区间跨度,需要明白如果 R 位置上是一个表首位置,还需要把这个表的尾部找到,将信息记录到最左边的位置(新表首),入栈记录即可。

SPLIT 根据记录的栈信息得到修改的区间跨度,需要给 s1 [L+1,R] 数值减一,给 s2 [L,R] 数值减一。

QUERY_LENGTH 只需要输出 s1 最小值为 的最小值数量。

QUERY_DEPTH 只需要输出 s2 的最大值即可。

C++ 代码:

#include<stack>
#include<string>
#include<vector>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
class Segment_Tree_Method
{
	struct node{int max_val,min_val,min_cnt,tag;};
	struct Segment_Tree
	{
		vector<node> x;
#define mid (l+r>>1)
#define ls (2*p)
#define rs (2*p+1)
		void pushup(int p)
		{
			if(x[ls].min_val==x[rs].min_val)
			{
				x[p].min_val=x[ls].min_val;
				x[p].min_cnt=x[ls].min_cnt+x[rs].min_cnt;
			}
			else
			{
				int min_son=x[ls].min_val<x[rs].min_val?ls:rs;
				x[p].min_val=x[min_son].min_val;
				x[p].min_cnt=x[min_son].min_cnt;
			}
			x[p].max_val=max(x[ls].max_val,x[rs].max_val);
		}
		void modify_node(int p,int val)
		{
			x[p].min_val+=val;
			x[p].max_val+=val;
			x[p].tag+=val;
		}
		void pushdown(int p)
		{
			if(x[p].tag!=0)
			{
				modify_node(ls,x[p].tag);
				modify_node(rs,x[p].tag);
				x[p].tag=0;
			}
		}
		void build(int p,int l,int r)
		{
			if(l==r)return x[p]={1,1,1,0},void();
			build(ls,l,mid);
			build(rs,mid+1,r);
			pushup(p);
		}
		void add(int p,int l,int r,int L,int R,int V)
		{
			if(L<=l&&r<=R)
			{
				modify_node(p,V);
				return;
			}
			pushdown(p);
			if(L<=mid)add(ls,l,mid,L,R,V);
			if(R>mid)add(rs,mid+1,r,L,R,V);
			pushup(p);
		}
		int Kth_pos_min_eq1(int p,int l,int r,int K)
		{
			if(l==r)return l;
			pushdown(p);
			int ls_cnt=(x[ls].min_val==1)*x[ls].min_cnt;
			if(K<=ls_cnt)return Kth_pos_min_eq1(ls,l,mid,K);
			else return Kth_pos_min_eq1(rs,mid+1,r,K-ls_cnt);
		}
#undef mid
#undef ls
#undef rs
	};
	Segment_Tree s1,s2;
	vector<stack<int,vector<int>>> right_pos;
	int n;
	/*
	std::stack<int> 的默认底层容器是 std::deque<int>,
	而单个 std::deque<int> 只有一个元素也要占用不小的空间,
	详见 https://zh.cppreference.com/w/cpp/container/deque
	所以,如果同时在多个 std::deque<int> 加入元素会使用一大堆空间,
	好在本题空间较大,此处使用默认底层容器 std::deque<int> 也并无大碍。
	std::stack<int,std::vector<int>>意味底层容器是 std::vector<int>,
	后者空间使用量明显小于前者。
	*/
public:
	void init(int _n,int m)
	{
		n=_n;
		s1.x.resize(4*n);s1.build(1,1,n);
		s2.x.resize(4*n);s2.build(1,1,n);
		right_pos.resize(n+1);
	}
	int query_length(){return (s1.x[1].min_val==1)*s1.x[1].min_cnt;}
	int query_depth(){return s2.x[1].max_val;}
	void merge(int l,int r)
	{
		int L=s1.Kth_pos_min_eq1(1,1,n,l);
		int R=s1.Kth_pos_min_eq1(1,1,n,r);
		if(!right_pos[R].empty())R=right_pos[R].top();//连续覆盖
		right_pos[L].push(R);
		if(L<R)s1.add(1,1,n,L+1,R,1);
		s2.add(1,1,n,L,R,1);
	}
	void split(int pos)
	{
		int L=s1.Kth_pos_min_eq1(1,1,n,pos);
		if(right_pos[L].empty())return;
		int R=right_pos[L].top();right_pos[L].pop();
		if(L<R)s1.add(1,1,n,L+1,R,-1);
		s2.add(1,1,n,L,R,-1);
	}
//	private:void print(int p)
//	{
	
//	}
//	public:void print()
//	{
//		cout<<"@"<<query_length()<<' '<<query_depth()<<endl;
//	}
};
Segment_Tree_Method L;
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	int n,m;cin>>n>>m;
	L.init(n,m);
//	L.print();
	while(m--)
	{
		string s;cin>>s;
		if(s[0]=='M')
		{
			int l,r;cin>>l>>r;
			L.merge(l,r);
		}
		else if(s[0]=='S')
		{
			int x;cin>>x;
			L.split(x);
		}
		else if(s[0]=='Q')
		{
//			cout<<"@"<<s<<' '<<m<<' ';
			if(s[6]=='L')cout<<L.query_length()<<'\n';
			else if(s[6]=='D')cout<<L.query_depth()<<'\n';
		}
//		L.print();
	}
	return 0;
}

Treap

维护查询位次、分裂、合并操作。

每次 MERGE 操作把对应一块劈出来,新建一个结点来表示子表,SPLIT 操作再把找到的子表主体并回去。

C++ 代码:

#include<string>
#include<vector>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{int dat,ls,rs,vs,size,dep;};
//dat:优先程度标记,此处没有用随机赋值的方式
//ls:左子结点编号
//rs:右子结点编号
//vs:虚子结点编号,用于记录寄存在该位置上的子表
//size:子树结点数
//dep:子树上的全体子表/元素深度,元素深度为1
class Treap
{
	vector<node> x;int root;
	void pushup(int p)
	{
		x[p].dep=max({x[x[p].ls].dep,x[x[p].rs].dep,x[x[p].vs].dep+1});
		x[p].size=x[x[p].ls].size+x[x[p].rs].size+1;
	}
	int build(int l,int r,int d)
	{
		int mid=l+r>>1;
		x[mid].dat=d;
		if(l<=mid-1)x[mid].ls=build(l,mid-1,d+1);
		if(mid+1<=r)x[mid].rs=build(mid+1,r,d+1);
		return pushup(mid),mid;
	}
	void _split(int p,int k,int&t1,int&t2)
	{
		//分成[1,k]和[k+1,total size]两部分
		if(p==0)return t1=t2=0,void();
		if(x[x[p].ls].size+1>k)t2=p,_split(x[p].ls,k,t1,x[p].ls);
		else t1=p,_split(x[p].rs,k-(x[x[p].ls].size+1),x[p].rs,t2);
		pushup(p);
	}
	int _merge(const int t1,const int t2)
	{
		if(t1==0||t2==0)return t1|t2;
		if(x[t1].dat<x[t2].dat)//dat 小根
		{
			x[t1].rs=_merge(x[t1].rs,t2);
			return pushup(t1),t1;
		}
		else
		{
			x[t2].ls=_merge(t1,x[t2].ls);
			return pushup(t2),t2;
		}
	}
	int new_node(int sr)//subtree_root
	{
		x.push_back({x[sr].dat,0,0,sr,1,x[sr].dep+1});
		return x.size()-1;
	}
	vector<int> find_kth_path(int now,int k)
	{
		vector<int>path={now};
		while(now)
		{
			if(k<=x[x[now].ls].size)now=x[now].ls;
			else
			{
				k-=x[x[now].ls].size+1;
				if(k==0)break;
				now=x[now].rs;
			}
			path.push_back(now);
		}
		return path;
	}
public:
	void init(int n,int m)
	{
		x.reserve(n+m+1);x.resize(n+1);
		root=build(1,n,0);
	}
	int query_length(){return x[root].size;}
	int query_depth(){return x[root].dep;}
	void merge(int l,int r)
	{
		assert(r<=query_length());
		int t1,t2,t3;
		_split(root,r,t2,t3);
		_split(t2,l-1,t1,t2);
		root=_merge(_merge(t1,new_node(t2)),t3);
	}
	void split(int pos)
	{
		assert(pos<=query_length());
		vector<int>&&path=find_kth_path(root,pos);
		int p=path.back();
		if(x[p].vs!=0)
		{
			int vs=x[p].vs;
			swap(x[p],x[x[p].vs]);
			x[p].ls=_merge(x[vs].ls,x[p].ls);
			x[p].rs=_merge(x[p].rs,x[vs].rs);
			for(int i=path.size()-1;i>=0;i--)pushup(path[i]);
		}
	}
//	private:void print(int p)
//	{
//		if(p==0)return;
//		print(x[p].ls);
//		if(x[p].vs)cout<<'(',print(x[p].vs),cout<<')';
//		else cout<<'*';
//		print(x[p].rs);
//	}
//	public:void print()
//	{
//		cout<<"(";
//		print(root);
//		cout<<")"<<endl;
//	}
};
Treap L;
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	int n,m;cin>>n>>m;
	L.init(n,m);
//	L.print();
	while(m--)
	{
		string s;cin>>s;
		if(s[0]=='M')
		{
			int l,r;cin>>l>>r;
			L.merge(l,r);
		}
		else if(s[0]=='S')
		{
			int x;cin>>x;
			L.split(x);
		}
		else if(s[0]=='Q')
		{
			if(s[6]=='L')cout<<L.query_length()<<'\n';
			else if(s[6]=='D')cout<<L.query_depth()<<'\n';
		}
//		L.print();
	}
	return 0;
}

Splay

均摊近似线性方法。

#include<string>
#include<vector>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{union{int son[2];struct{int ls,rs;};};int fa,vs,size,dep;};
//size:子树结点数
//ls/son[0]:左子结点编号
//rs/son[1]:右子结点编号
//fa:父结点编号
//vs:虚子结点编号,用于记录寄存在该位置上的子表
//dep:子树上的全体子表/元素深度,元素深度为1
class SplayTree
{
	vector<node> x;int root;
	void pushup(int p)
	{
		x[p].dep=max({x[x[p].ls].dep,x[x[p].rs].dep,x[x[p].vs].dep+1});
		x[p].size=x[x[p].ls].size+x[x[p].rs].size+1;
	}
	int build(int l,int r)
	{
		int mid=l+r>>1;
		if(l<=mid-1)x[x[mid].ls=build(l,mid-1)].fa=mid;
		if(mid+1<=r)x[x[mid].rs=build(mid+1,r)].fa=mid;
		return pushup(mid),mid;
	}
	int new_node(int sr)//subtree_root
	{
		x.push_back({{0,0},x[sr].fa,sr,1,x[sr].dep+1});
		return static_cast<int>(x.size())-1;
	}
#define get(xx) (xx==x[x[xx].fa].rs)
	void rotate(int p)
	{
		int f=x[p].fa;bool ws=get(p);
		if(x[f].fa)x[x[f].fa].son[get(f)]=p;x[p].fa=x[f].fa;
		x[x[f].son[ws]=x[p].son[!ws]].fa=f;
		x[x[p].son[!ws]=f].fa=p;
		pushup(f),pushup(p);
	}
	void splay(int p,int target=0)
	{
		for(int f;(f=x[p].fa)!=target;rotate(p))
			if(x[f].fa!=target)rotate(get(p)==get(f)?f:p);
		if(target==0)root=p;
	}
	int kth(int k)
	{
		int now=root;
		while(now)
		{
			if(k<=x[x[now].ls].size)now=x[now].ls;
			else
			{
				k-=x[x[now].ls].size+1;
				if(k==0)break;
				now=x[now].rs;
			}
		}
		return now;
	}//询问完kth后续要splay求出的结点
public:
	void init(int n,int m)
	{
		x.reserve(n+m+1+2);x.resize(n+1+2);
		root=build(1,n+2);
	}
	int query_length(){return x[root].size-2;}
	int query_depth(){return x[root].dep;}
	void merge(int l,int r)
	{
		assert(r<=query_length());
		l++;r++;
		int nl=kth(l-1);splay(nl);
		int nr=kth(r+1);splay(nr,nl);
		splay(x[nr].ls=new_node(x[nr].ls));
	}
	void split(int pos)
	{
		assert(pos<=query_length());
		pos++;
		int nl=kth(pos-1);splay(nl);
		int nr=kth(pos+1);splay(nr,nl);
		int p=x[nr].ls;
		if(x[p].vs!=0)
			x[x[nr].ls=x[p].vs].fa=nr,
			splay(x[nr].ls);
	}
//	private:void print(int p)
//	{
//		if(p==0)return;
//		print(x[p].ls);
//		if(x[p].vs)cout<<'(',print(x[p].vs),cout<<')';
//		else cout<<'*';
//		print(x[p].rs);
//	}
//	public:void print()
//	{
//		cout<<"(";
//		print(root);
//		cout<<")"<<endl;
//	}
};
SplayTree L;
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	int n,m;cin>>n>>m;
	L.init(n,m);
//	L.print();
	while(m--)
	{
		string s;cin>>s;
		if(s[0]=='M')
		{
			int l,r;cin>>l>>r;
			L.merge(l,r);
		}
		else if(s[0]=='S')
		{
			int x;cin>>x;
			L.split(x);
		}
		else if(s[0]=='Q')
		{
			if(s[6]=='L')cout<<L.query_length()<<'\n';
			else if(s[6]=='D')cout<<L.query_depth()<<'\n';
		}
//		L.print();
	}
	return 0;
}

两种分块方法

记录 上的深度, 上深度为 的位置等。

维护查询位次、区间加、区间减等操作。注意不要写假了。

MERGE 给对应区间深度加上 ,这里可以考虑在不更改头的位置的深度用来保证位次查询正确,以此可以分出来两种做法,见下面代码。

SPLIT 找到之前 MERGE 的时候的区间,深度减 ,同时维护深度加减和深度为 可能会写假,所以细节比较多。

C++ 代码(其一):

#include<cmath>
#include<stack>
#include<string>
#include<vector>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
class Blocks
{
	int Bs,Brs,num;//Block size,Brink size
	vector<vector<int>> depth[2],Bdepth_rec[2];
	vector<int> Bdepth_base;
	vector<stack<int,vector<int>>> right_pos;
	/*
	std::stack<int> 的默认底层容器是 std::deque<int>,
	而单个 std::deque<int> 只有一个元素也要占用不小的空间,
	详见 https://zh.cppreference.com/w/cpp/container/deque
	所以,如果同时在多个 std::deque<int> 加入元素会使用一大堆空间,
	好在本题空间较大,此处使用默认底层容器 std::deque<int> 也并无大碍。
	std::stack<int,std::vector<int>>意味底层容器是 std::vector<int>,
	后者空间使用量明显小于前者。
	*/
	void depth_add(int a,int i,int j)
	{
		int&t=depth[a][i][j];
		if(t+1==Bdepth_rec[a][i].size())
			Bdepth_rec[a][i].push_back(0);
		Bdepth_rec[a][i][t]--;
		Bdepth_rec[a][i][++t]++;
	}
	bool depth_eq1(int i,int j)const
	{
		return Bdepth_base[i]+depth[1][i][j]==1;
	}
	int Bdepth_eq1(int i)const
	{
		if(Bdepth_base[i]>1)return 0;
		else return Bdepth_rec[1][i][1-Bdepth_base[i]];
	}
	int Bsize(int i)const{return i==num-1?Brs:Bs;}
	pair<int,int> kth(int k)//序数从0开始
	{
		int i=0,j=0;
		for(;i<num;i++)
		{
			int t=Bdepth_eq1(i);
			if(k>=t)k-=t;else break;
		}
		for(;j<Bsize(i);j++)
		{
			if(depth_eq1(i,j))
			{
				if(k==0)break;
				else k--;
			}
		}
		return{i,j};
	}
public:
	void init(int n,int m)
	{
		Bs=ceil(sqrt(n));//简简单***方根分块,不一定最佳,但够用
		if(n%Bs==0)Brs=Bs;else Brs=n%Bs;
		num=(n+Bs-1)/Bs;//上取整
		for(int a=0;a<=1;a++)
		{
			depth[a].resize(num);
			for(int i=0;i<num-1;i++)depth[a][i].resize(Bs);
			depth[a].back().resize(Brs);
		}
		for(int a=0;a<=1;a++)
		{
			Bdepth_rec[a].resize(num);
			for(int i=0;i<num-1;i++)Bdepth_rec[a][i]={Bs};
			Bdepth_rec[a].back()={Brs};
		}
		Bdepth_base.resize(num,1);
		right_pos.resize(n);
	}
	void merge(int l,int r)
	{
		l--;r--;//下标从0开始
		auto[L,I]=kth(l);
		auto[R,J]=kth(r);
		if(right_pos[R*Bs+J].size())
		{
			auto[R1,J1]=div(right_pos[R*Bs+J].top(),Bs);
			R=R1;J=J1;
		}
		right_pos[L*Bs+I].push(R*Bs+J);
		if(L==R)
		{
			for(int a=0;a<=1;a++)
			{
				for(int i=I+a;i<=J;i++)depth_add(a,L,i);
			}
		}
		else
		{
			for(int i=L+1;i<R;i++)Bdepth_base[i]++;
			for(int a=0;a<=1;a++)
			{
				for(int i=I+a;i<Bs;i++)depth_add(a,L,i);
				for(int i=0;i<=J;i++)depth_add(a,R,i);
			}
		}
	}
	void split(int pos)
	{
		pos--;//下标从0开始
		auto[L,I]=kth(pos);
		auto&st=right_pos[L*Bs+I];
		if(st.size())
		{
			int r=st.top();st.pop();
			auto[R,J]=div(r,Bs);
			if(L==R)
			{
				Bdepth_base[L]--;
				for(int a=0;a<=1;a++)
				{
					for(int i=0;i<I+a;i++)depth_add(a,L,i);
					for(int i=J+1;i<Bsize(L);i++)depth_add(a,L,i);
				}
			}
			else
			{
				Bdepth_base[L]--;
				for(int i=L+1;i<R;i++)Bdepth_base[i]--;
				Bdepth_base[R]--;
				for(int a=0;a<=1;a++)
				{
					for(int i=0;i<I+a;i++)depth_add(a,L,i);
					for(int i=J+1;i<Bsize(R);i++)depth_add(a,R,i);
				}
			}
		}
	}
	int query_length()
	{
		int length=0;
		for(int i=0;i<num;i++)length+=Bdepth_eq1(i);
		return length;
	}
	int query_depth()
	{
		int maxdepth=0;
		for(int i=0;i<num;i++)
			maxdepth=max(maxdepth,
				int(Bdepth_base[i]+Bdepth_rec[0][i].size()-1));
		return maxdepth;
	}
//	void print()
//	{
//		cout<<"@"<<query_length()<<endl;
//		for(int i=0;i<num;i++)
//			for(int j=0;j<Bsize(i);j++)
//				cout<<Bdepth_base[i]+depth[0][i][j]<<' ';
//		cout<<endl;
//		for(int i=0;i<num;i++)
//			for(int j=0;j<Bsize(i);j++)
//				cout<<Bdepth_base[i]+depth[1][i][j]<<' ';
//		cout<<endl;
//	}
};
Blocks L;
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	int n,m;cin>>n>>m;
	L.init(n,m);
//	L.print();
	while(m--)
	{
		static int cnt=0;
		string s;cin>>s;
		if(s[0]=='M')
		{
			int l,r;cin>>l>>r;
			L.merge(l,r);
		}
		else if(s[0]=='S')
		{
			int x;cin>>x;
			L.split(x);
		}
		else if(s[0]=='Q')
		{
			if(s[6]=='L')cout<<L.query_length()<<'\n';
			else if(s[6]=='D')cout<<L.query_depth()<<'\n';
		}
//		L.print();
	}
	return 0;
}

C++ 代码(其二):

#include<set>
#include<cmath>
#include<stack>
#include<string>
#include<vector>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
using pii=pair<int,int>;
class Blocks
{
	int Bs,Brs,num;//Block size,Brink size
	vector<vector<int>> depth,Bdepth_rec;
	vector<int> Bdepth_base;
	vector<stack<pii,vector<pii>>> list_info;
	vector<multiset<int>> Bdepthes;
	void depth_add(int i,int j)
	{
		int&t=depth[i][j];
		if(t+1==Bdepth_rec[i].size())
			Bdepth_rec[i].push_back(0);
		Bdepth_rec[i][t]--;
		Bdepth_rec[i][++t]++;
	}
	bool depth_eq1(int i,int j)const
	{
		return Bdepth_base[i]+depth[i][j]==1;
	}
	int Bdepth_eq1(int i)const
	{
		if(Bdepth_base[i]>1)return 0;
		else return Bdepth_rec[i][1-Bdepth_base[i]];
	}
	int get_maxdepth(int i,int j)const
	{
		if(list_info[i*Bs+j].size())
			return list_info[i*Bs+j].top().second;
		return 1;
	}
	int get_Bmaxdepth(int i)
	{
		return *--Bdepthes[i].end();
	}
	int Bsize(int i)const{return i==num-1?Brs:Bs;}
	pair<int,int> kth(int k)//0-indexed
	{
		int i=0,j=0;
		for(;i<num;i++)
		{
			int t=Bdepth_eq1(i);
			if(k>=t)k-=t;else break;
		}
		for(;j<Bsize(i);j++)
		{
			if(depth_eq1(i,j))
			{
				if(k==0)break;
				else k--;
			}
		}
		return{i,j};
	}
public:
	void init(int n,int m)
	{
		Bs=ceil(sqrt(n));
		if(n%Bs==0)Brs=Bs;else Brs=n%Bs;
		num=(n+Bs-1)/Bs;//上取整
		//
		depth.resize(num);
		for(int i=0;i<num-1;i++)depth[i].resize(Bs);
		depth.back().resize(Brs);
		//
		Bdepth_rec.resize(num);
		for(int i=0;i<num-1;i++)Bdepth_rec[i]={Bs};
		Bdepth_rec.back()={Brs};
		//
		Bdepth_base.resize(num,1);
		//
		list_info.resize(n);
		//
		Bdepthes.resize(num);
		for(int i=0;i<num;i++)Bdepthes[i].insert(1);
	}
	void merge(int l,int r)
	{
		l--;r--;
		auto[L,I]=kth(l);//cout<<"1$ "<<L<<" "<<I<<endl;
		auto[R,J]=kth(r);//cout<<"2$ "<<R<<" "<<J<<endl;
		if(list_info[R*Bs+J].size())
		{
			auto[R1,J1]=div(list_info[R*Bs+J].top().first,Bs);
			R=R1;J=J1;
		}
		int maxdepth=get_maxdepth(L,I);
		if(L==R)
		{
			for(int i=I+1;i<=J;i++)
			{
				depth_add(L,i);
				maxdepth=max(maxdepth,get_maxdepth(L,i));
			}
		}
		else
		{
			for(int i=I+1;i<Bs;i++)
			{
				depth_add(L,i);
				maxdepth=max(maxdepth,get_maxdepth(L,i));
			}
			for(int i=L+1;i<R;i++)
			{
				Bdepth_base[i]++;
				maxdepth=max(maxdepth,get_Bmaxdepth(i));
			}
			for(int i=0;i<=J;i++)
			{
				depth_add(R,i);
				maxdepth=max(maxdepth,get_maxdepth(R,i));
			}
		}
		Bdepthes[L].insert(maxdepth+1);
		list_info[L*Bs+I].emplace(R*Bs+J,maxdepth+1);
	}
	void split(int pos)
	{
		pos--;
		auto[L,I]=kth(pos);
		auto&st=list_info[L*Bs+I];
		if(st.size())
		{
			auto[r,t_depth]=st.top();st.pop();
			Bdepthes[L].erase(Bdepthes[L].lower_bound(t_depth));
			auto[R,J]=div(r,Bs);
			if(L==R)
			{
				Bdepth_base[L]--;
				for(int i=0;i<=I;i++)depth_add(L,i);
				for(int i=J+1;i<Bsize(L);i++)depth_add(L,i);
			}
			else
			{
				Bdepth_base[L]--;for(int i=0;i<=I;i++)depth_add(L,i);
				for(int i=L+1;i<R;i++)Bdepth_base[i]--;
				Bdepth_base[R]--;for(int i=J+1;i<Bsize(R);i++)depth_add(R,i);
			}
		}
	}
	int query_length()
	{
		int length=0;
		for(int i=0;i<num;i++)length+=Bdepth_eq1(i);
		return length;
	}
	int query_depth()
	{
		int maxdepth=0;
		for(int i=0;i<num;i++)
			maxdepth=max(maxdepth,get_Bmaxdepth(i));
		return maxdepth;
	}
//	void print()
//	{
//		cout<<"@"<<query_length()<<endl;
//		for(int i=0;i<num;i++)
//			for(int j=0;j<Bsize(i);j++)
//				cout<<Bdepth_base[i]+depth[i][j]<<' ';
//		cout<<endl;
//	}
};
Blocks L;
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	int n,m;cin>>n>>m;
	L.init(n,m);
//	L.print();
	while(m--)
	{
		static int cnt=0;
		string s;cin>>s;
		if(s[0]=='M')
		{
			int l,r;cin>>l>>r;
			L.merge(l,r);
		}
		else if(s[0]=='S')
		{
			int x;cin>>x;
			L.split(x);
		}
		else if(s[0]=='Q')
		{
			if(s[6]=='L')cout<<L.query_length()<<'\n';
			else if(s[6]=='D')cout<<L.query_depth()<<'\n';
		}
//		L.print();
	}
	return 0;
}

__gnu_cxx::rope 做法

注:__gnu_cxx::rope 是 GNU 的扩展库容器,它不是 PBDS(PBDS 对应的命空间是 __gnu_pbds),不是标准库内容,更不是 STL。__gnu_cxx::rope 是一个比较复杂的(复杂到有一些不好找和改的 BUG)容器,不太好归到哪种数据结构分类中。

代码是从好兄弟那里拿来的,空间用得非常极限,懂 __gnu_cxx::rope 还是很容易想的。区间查询/修改用了线段树。

#include<bits/stdc++.h>
#include<ext/rope>
#define int long long
//define覆盖关键字是比较邪门的写法,请尽量用 typedef 或 using 套一个合适标识符代替
using namespace std;
using namespace __gnu_cxx;
const int N = 4e5+10;
const int maxn=4e5+10;
stack<pair<int,int>>sc[N];
int  q, n, k, l, r, x, y, ne[N], b[N];
stack<pair<int,int>>pre[N];
rope<int> S[N];
int ans1, ans2;
long long lowbit(long long x) {
	return (-x)&x;
}
struct node{
	long long sum,add,mul;
	int l,r;
	bool flag;
}tr[4*maxn];
vector<long long>a(maxn);
int op,cnt;
void pushup(int x){
	tr[x].sum=max(tr[x*2].sum,tr[x*2+1].sum);
}
void eval(int x,long long add){
	tr[x].sum+=add;
}
void pushdown(int x){
	eval(x*2,tr[x].add);
	eval(x*2+1,tr[x].add);
	tr[x*2].add+=tr[x].add;
	tr[x*2+1].add+=tr[x].add;       
	tr[x].add=0;
}
void build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r;
	tr[x].flag=0,tr[x].add=0;
	if(l==r){
		tr[x].sum=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(x*2,l,mid);
	build(x*2+1,mid+1,r);
	pushup(x);
}
long long query(int x,int l,int r){
	if(tr[x].l>=l&&tr[x].r<=r)return tr[x].sum;
	pushdown(x);
	int mid=(tr[x].l+tr[x].r)/2;
	long long sum=-1e18;
	if(l<=mid)sum=max(sum,query(x*2,l,r));
	if(r>mid)sum=max(query(x*2+1,l,r),sum);
	pushup(x);
	return sum;
}
void update(int now,int l,int r,long long add){
	if(l<=tr[now].l&&r>=tr[now].r){
		tr[now].add+=add;
		eval(now,add);
		}
	else {
		pushdown(now);
		int mid=(tr[now].l+tr[now].r)/2;
		if(l<=mid)update(now*2,l,r,add);
		if(r>mid)update(now*2+1,l,r,add);
		pushup(now);
	}
}
void solve(){
	cnt = 0;
	cin >> n >> q;
	ans1 = n;
	for(int i = 0; i <= n; i++){
		a[i] = 1;
		S[0].push_back(i);
	}
	build(1,1,n);
	string op;
	for(int i = 1; i <= q; i++){
		cin >> op;
		if(op == "MERGE"){
			S[cnt + 1] = S[cnt];
			cnt++;
			cin >> x >> y;
			int xx = S[cnt][x], yy = S[cnt][y];
			if(sc[yy].size() > 0) yy = sc[yy].top().second;
			ans1 -= y - x;
			sc[xx].push({y - x, yy});
			update(1,xx,yy,1);
			S[cnt].erase(x + 1, y - x);
			pre[xx].push({cnt - 1, x});
		}
		if(op == "SPLIT"){
			cin >> x;
			int xx = S[cnt][x];
			if(sc[xx].size() == 0) continue;
			S[cnt + 1] = S[cnt];
			cnt++;
			int z = sc[xx].top().first, yy = sc[xx].top().second;
			sc[xx].pop();
			ans1 += z;
			update(1,xx,yy,-1);
			S[cnt].insert(x + 1,S[pre[xx].top().first].substr(pre[xx].top().second + 1, z));
			pre[xx].pop();
		}
		if(op == "QUERY_LENGTH"){
			cout << ans1 << '\n';
		}
		if(op == "QUERY_DEPTH"){
			cout << query(1,1,n) << '\n';
		}
	}
}
signed main(){
	//freopen("output.txt", "r", stdin);
	//freopen("std.txt", "w", stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t = 1;
	// cin >> t;
	while(t--){
		solve();
	}
	return 0;
}

其他做法

待补充……