P6864 [RC-03] 記憶 題解(評分:8.1)(2023.12.13)

Fun_Strawberry發表於2024-04-17

前言

這下又不是官解了吧?

模擬賽題,在一個月後又出現在了資料結構講稿上,令人忍俊不禁。

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.

相關文章