[WC2023]楼梯 解题报告

在开始正文前,先分享一下笔者在 WC2023 的悲惨经历。

前 2.5h 在三题间反复横跳,啥都不会,心态爆炸,也根本没写什么暴力分;后 2.5h 猛然发现这题会做,没有想清楚代码细节就开始狂暴 rush,结果没冲出来。

于是悲惨打铜。

还不如先睡他 3.5h,然后起床花 1.5h 写三个暴力直接 Au 跑路!

经过两天,也差不多冷静下来了。希望自己能够知耻而后勇,在考场上享受比赛,做出正确的决策,心态稳健。

Solution

先考虑一个弱化的问题,怎么对一个静态的楼梯做 \(q\) 次查询操作。

我们可以用“楼梯序列”描述一个楼梯——每当遇到一个右边界,就在序列末尾加入一个 \(1\),每遇到一个下边界,就在序列末尾加入 \(0\)

记这样的序列为 \(A\),序列的长度为 \(n\)

考虑上图,有 \(A=[1,1,0,1,0,0,1,1,0],n=9\)

容易发现这是个双射。

而楼梯序列中的子区间 \([l,r]\) 代表了原结构中的子楼梯。具体地,一个满足 \(A_l=1,A_r=0\) 的区间 \([l,r]\),和原结构以网格 \((\sum_{i=1}^{l} A_i,\sum_{j=r}^{n}1-A_j)\) 作为生成格产生的子楼梯是一个双射。

举几个例子,区间 \([2,3]\) 代表着以 \((2,4)\) 为生成格的子楼梯,区间 \([4,6]\) 代表了以 \((3,2)\) 为生成格的子楼梯。

在下文中,我们都将用子楼梯 \([l,r]\) 来描述这个区间映射到的子楼梯。

这样转换有什么好处呢?

一是,把一个二维空间上的问题压缩到了一维的序列上,方便用数据结构维护。

二是,注意到所谓的“边界格数”,实际上就是 \(\frac{X}{2}-1\),其中 \(X\) 代表楼梯的周长。对于子楼梯 \([l,r]\),这个值也就等于 \(r-l\),我们可以非常方便地算出边界格数。

考虑 ? q 怎么做,我们拎出 \(A_1,A_{1+q},A_{1+2q},\dots,A_{n-q},A_{n}\),记其为 \(B\),项数为 \(m\)

如果 \(B\) 中存在相邻的 \(1,0\),我们就找到了一组解。

又因为 \(A_1=1,A_n=0\),我们可以断言,若 \(n>0\),一定有解。至于 \(n=0\) 的情况,则是无解。

考察 \(mid=\lfloor{\frac{1+m}{2}}\rfloor\),若有 \(B_{mid}=0\),则在 \([1,mid]\) 中必有一组解;反之,则在 \([mid,m]\) 中必有一组解。

这是一个可以二分的结构,查询就做完了。

初始我们先认为序列里有无穷多的 \(1\),这样更方便维护,这相当于把坐标轴也考虑了进来。当然,在查询的时候要去除多余的 \(1\)

考虑剩下的操作怎么维护。

  • + a b 这相当于在第 \(a\)\(1\) 后面加入 \(b\)\(0\)

  • - a b 我们要去掉序列中后 \(b\)\(0\),再在第 \(a\)\(1\) 前加入 \(b\)\(0\)。特别地,如果在第 \(a\)\(1\) 之后的 \(0\) 的数量不足 \(b\),要另外处理一下。

  • R u 由于保证前 \(u\) 个操作没有 \(R\),暴力还原或者栈序撤销显然是对的,但感觉直接可持久化会更好写。

以上所有操作都可以使用线段树维护——记录每个 \(1\) 与下一个 \(1\) 之间有多少个 \(0\)

时间复杂度 \(O(nlog^2V)\)。在这里,\(V=10^9\)

Code

p.s. 这份代码暂时没地方测试,不过大样例输出应该是对的。

#include<bits/stdc++.h>
#define IL inline
#define reg register
#define N 300300
#define M 10001000
#define int long long
IL int read()
{
	reg int x=0; reg char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x;
}

int n,len=1e9+5;
char s[10];
int rt[N],ls[M],rs[M],sum[M],tot;

IL int clone(reg int x){return ls[++tot]=ls[x],rs[tot]=rs[x],sum[tot]=sum[x],tot;}

void modify(reg int &o,reg int l,reg int r,reg int p,reg int k)
{
	sum[o=clone(o)]+=k;
	if(l==r)return; reg int mid=l+r>>1;
	if(mid>=p)modify(ls[o],l,mid,p,k); else modify(rs[o],mid+1,r,p,k);
}

int suf_sum(reg int o,reg int l,reg int r,reg int p)
{
	if(!o||p>r)return 0;
	if(p<=l)return sum[o]; reg int mid=l+r>>1;
	return suf_sum(ls[o],l,mid,p)+suf_sum(rs[o],mid+1,r,p);
}

int dfs(reg int o,reg int l,reg int r,reg int k)
{
	if(l==r)return l; reg int mid=l+r>>1;
	if(sum[rs[o]]>=k)return dfs(rs[o],mid+1,r,k);
	return dfs(ls[o],l,mid,k-sum[rs[o]]);
}

void clear(reg int &o,reg int l,reg int r,reg int p)
{
	if(p>r||!o)return;
	if(p<=l)return o=0,void(); reg int mid=l+r>>1;
	o=clone(o),clear(ls[o],l,mid,p),clear(rs[o],mid+1,r,p);
	sum[o]=sum[ls[o]]+sum[rs[o]];
}

int query(reg int o,reg int l,reg int r,reg int k)
{
	if(l==r)return k==1?l:-l; reg int mid=l+r>>1;
	if(mid-l+1+sum[ls[o]]>=k)return query(ls[o],l,mid,k);
	return query(rs[o],mid+1,r,k-(mid-l+1+sum[ls[o]]));
}

void dc(reg int l,reg int r,reg int d,reg int rt)
{
	if(l==r-1)
	{
		reg int p=query(rt,1,len,l*d+1),q=-query(rt,1,len,r*d+1);
		return printf("%lld %lld\n",p,suf_sum(rt,1,len,p)-(d-q+p)+1),void();	
	}
	reg int mid=l+r>>1;
	if(query(rt,1,len,mid*d+1)>0)dc(mid,r,d,rt); else dc(l,mid,d,rt);
}

main()
{
	n=read();
	for(reg int a,b,p,k,i=1;i<=n;++i)
	{
		scanf("%s",s),a=read(),rt[i]=rt[i-1];
		if(s[0]=='+')modify(rt[i],1,len,a,read());
		else if(s[0]=='-')
		{
			b=read();
			if(sum[rt[i]]<=b&&a==1){rt[i]=0; continue;}
			p=dfs(rt[i],1,len,b);
			if(p>=a)
			{
				modify(rt[i],1,len,p,suf_sum(rt[i],1,len,p+1)-b),clear(rt[i],1,len,p+1);
				if(a>1)modify(rt[i],1,len,a-1,b);
			}else
			{
				if(a>1)modify(rt[i],1,len,a-1,suf_sum(rt[i],1,len,a));
				clear(rt[i],1,len,a);
			}
		}else if(s[0]=='R')rt[i]=rt[i-a-1];
		else
		{
			p=dfs(rt[i],1,len,1),k=p-1+sum[rt[i]];
			if(!k){puts("-1 -1"); continue;}
			dc(0,k/a,a,rt[i]);
		}
	}
}
posted @ 2023-01-19 09:48  Nesraychan  阅读(353)  评论(0)    收藏  举报