P6864 [RC-03] 记忆 题解(评分:8.1)(2023.12.13)

前言

这下又不是官解了吧?

模拟赛题,在一个月后又出现在了数据结构讲稿上,令人忍俊不禁。

Solution

官方解法是用线段树加矩阵,不过赛时的我显然没这么聪明,是想不到的。

赛时我就只知道先发掘一些答案的性质。

一、一些性质

首先,发现这个撤销操作比较棘手,考虑没有撤销操作的情况下,每一个新的操作对答案的贡献。

发现:对于每次操作二,对答案的贡献绝对只有 \(1\)

只是在开头结尾各自加上一个括号,从左括号 \(+1\)、右括号 \(-1\) 来看,前缀数组是时刻 \(\ge0\) 的,如果在开头结尾加上一个括号,那前缀数组就只有最后一位为 \(0\),也就是以第一个括号开头只能匹配结尾括号,反之亦然。

类似的,考虑操作一在结尾加上一组括号,增加的贡献一定是以最后括号结尾的,此时

(......)(......)(......)  +  ()

前面有几组括号贡献就是组数 \(+1\),例如上图中尾部加入括号,贡献为 \(4\)

换句话说,操作二贡献恒为 \(1\),操作一贡献取决于前面连续操作一的次数,贡献为连续次数 \(+1\)

有一种 OSU! 的美。

二、如何维护

你以为这道题就做完了?撤销操作才是本题的重点!

考虑撤销操作可能会对连续操作一的长度有影响。

比如说:

2 1 1 1 1 2 1 1 1 

撤销掉第二个操作二,会导致其左右两边操作一连起来。

就像打音游,本来某个位置一直断,有一次这里全连了,最后连击数就会变高。

而且还可以撤销撤销操作,就导致已经被撤销的操作二可能会打赢复活赛。

换句话说我们得维护一个关于操作二的链表,能支持随时在某个位置插入,删除和查询前面或者后面的第一个操作二。

但是直接写链表,操作一怎么办?

因为我们还需要能够快速得出两个位置之间的操作一次数,这是一个区间查询,链表上显然行不通。

但是偶然间想到一个复杂度稍逊但是也可以维护链表的东西:平衡树!

这道题的解法就显然了:

用 set 维护操作二的插入和删除,记录下每次操作二的位置之后,便可以用树状数组来查询其间操作一数量。

三、具体影响

末尾加操作二,简单,\(+1\) 即可。

末尾加操作一,用 set 找到上一个还活着的操作二,树状数组求出其间的操作一数量为 \(x\) 贡献为 \(x+1\)

中间插入或删除操作一:找到左右的操作二,剩下同上。

中间插入或删除操作二,看看这个操作二左右两端各有多长的操作一,简单计算即可。

复杂度显然为 \(O(n\log n)\)

这显然不是官方解法,而且显然比官方做法简单。

AC 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t[1234657],n,cnt;
void change(int x,int v)
{
	for(;x<=n+10;x+=x&-x) t[x]+=v;
}
int ask(int x)
{
	int ans=0;
	for(;x>0;x-=x&-x) ans+=t[x];
	return ans;
}
set<int> s,fs;
int i,j,op,x;
int ot[666666],ch[666666],lo[666666];
signed main()
{
	cin>>n;
	for(i=1;i<=n;i++)
	ch[i]=i;
	int ans=1,p=1;
	s.insert(0);fs.insert(0);
	s.insert(n+1);fs.insert(-n-1);
	for(i=1;i<=n;i++)
	{
		cin>>op;
		ot[i]=op;
		if(op==1)
		{	
			cnt++;
			lo[i]=cnt;
			change(cnt,1);
			int q=-(*fs.upper_bound(-cnt));
			ans+=ask(cnt)-ask(q)+1;
		}
		if(op==2)
		{
			ans++;
			cnt++;
			lo[i]=cnt;
			s.insert(cnt);fs.insert(-cnt);
		}
		if(op==3)
		{
			cin>>x;
			ch[i]=-ch[x];
			if(ch[i]>0)
			{
				int ty=ot[ch[i]],loc=lo[ch[i]];
				int fr=-(*fs.upper_bound(-loc)),bk=*s.upper_bound(loc);
				if(ty==1)
				{
					change(loc,1);
					ans+=ask(bk-1)-ask(fr)+1;
				}
				if(ty==2)
				{
					s.insert(loc);fs.insert(-loc);
					ans++;
					int c=ask(bk-1)-ask(fr);
					ans-=c*(c+3)/2;
					c=ask(loc-1)-ask(fr);
					ans+=c*(c+3)/2;
					c=ask(bk-1)-ask(loc);
					ans+=c*(c+3)/2;
				}
			}
			else
			{
				int ty=ot[-ch[i]],loc=lo[-ch[i]];
				int fr=-*fs.upper_bound(-loc),bk=*s.upper_bound(loc);
				if(ty==1)
				{
					change(loc,-1);
					ans-=ask(bk-1)-ask(fr)+2;
				}
				if(ty==2)
				{
					ans--;
					s.erase(loc);fs.erase(-loc);
					int c=ask(bk-1)-ask(fr);
					ans+=c*(c+3)/2;
					c=ask(loc-1)-ask(fr);
					ans-=c*(c+3)/2;
					c=ask(bk-1)-ask(loc);
					ans-=c*(c+3)/2;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

后记

这题解有一种为了一碗醋包了一盘饺子的美。

什么叫数据结构只会树状数组啊。

The End.

posted @ 2024-04-17 08:11  Fun_Strawberry  阅读(20)  评论(0)    收藏  举报