1. 程式人生 > 其它 >題解 CF1209G2 Into Blocks (hard version)

題解 CF1209G2 Into Blocks (hard version)

題意

一個序列是好的,當且僅當序列中相等的兩個數間所有數全相等。

你每次可將某個元素值對應的所有元素改成另一元素值。

現有 \(q\) 次操作,每次操作將第 \(x\) 個元素改為 \(y\)

求初始時及每次操作後將這個序列改成好的序列最少要改的位置數。

題解

對於每一個序列中的元素 \(k\),設其在序列中最左與最右的位置分別為 \(l_k,r_k\),我們希望 \([l_k,r_k]\) 中的元素全部相等。

所以初始時,最優方案是把序列分成最多的好序列,答案為「每個好序列長度減去其中出現最多的元素的出現次數」的總和。

我們考慮一種更嚴謹的描述方式。

\(a_i\) 為第 \(i\) 個元素的元素值,\(b_i\)

\(a_i\)\(a_{i+1}\) 被要求為同一元素的次數。

那麼對於每一個序列中的元素 \(k\),我們將 \(b\)\([l_k,r_k)\) 區間加 \(1\),不僅求出了 \(b\),還發現每個 \(b\) 中連續的非零區間以及緊隨其後的 \(1\)\(0\),恰好對應一個最後要求的好序列。

我們可以用 set 維護元素位置,線段樹維護 \(b\)

如果有修改呢?

我們繼承前面的思路,爭取線上段樹上多儲存一些資訊來回答每個詢問。

\(c_i\) 為第 \(i\) 個元素在序列中出現的次數,為了方便維護,我們只在該元素出現的第一個位置儲存 \(c\),其他位置為 \(0\)

由此可知,在最優解的情況下,一個長度大於 \(1\) 的好序列,最後一位的 \(c\) 一定為 \(0\),否則由 \(c\) 的定義就可將該好序列分成兩個好序列,矛盾。

因此,一個長度大於 \(1\) 的好序列內出現最多的元素的出現次數即為 \([l_k,r_k)\)\(c\) 的最大值。

由於存在修改,此時我們不能只是簡單地維護每個 \(b\) 中「連續的非零區間以及緊隨其後的 \(1\)\(0\)」。

我們將上面的 \(0\) 都替換為區間內最小值,即維護每個 \(b\) 中「連續的大於區間最小值的區間以及緊隨其後的 \(1\) 個最小值」,我們定義這樣的區間為「廣義好序列」。

記一個廣義好序列左側(不在序列裡)與右側(在序列裡)的最小值分別為該好序列的左右端點。

我們線上段樹上每個節點需要維護下列 \(6\) 個資訊。

\(laz\):區間加法懶標記。

\(mnb\):區間 \(b\) 的最小值。

\(mxc\):區間 \(c\) 的最大值。

\(num\):每個左右端點均在區間內的廣義好序列中「顏色出現最大次數」之和。

\(lmx\):左端點所在廣義好序列中目前顏色出現次數的最大值。

\(rmx\):右端點所在廣義好序列中目前顏色出現次數的最大值。

我們只需要維護插入和刪除兩種操作。

而這兩種操作的核心都為對 \(b\) 進行區間加操作,對 \(d\) 進行單點修改操作。

大體操作與普通線段樹無異,重點在於更新完兩個子節點後,如何合併更新當前點,我們一一考慮。

\(laz\):下傳時已經變為 \(0\),無需更新。

\(mnb\):兩子節點 \(mnb\)\(\min\)

\(mxc\):兩子節點 \(mxc\)\(\max\)

接下來就有點棘手了,我們考慮兩子節點 \(mnb\) 的大小關係。

\(lson\) 為左兒子,\(rson\) 為右兒子,當前節點為 \(x\)

如果 \(lson.mnb\) 大於 \(rson.mnb\),說明 \(lson.mnb\) 大於區間內最小值,亦即左區間內不可能有左右端點均在區間內的廣義好序列。

所以 \(x.num=rson.num\)\(x.rmx=rson.rmx\)

而此時左端點所在廣義好序列的右邊界在左區間的右側,因此當前點「左端點所在廣義好序列中目前顏色出現次數的最大值」為右兒子「左端點所在廣義好序列中目前顏色出現次數的最大值」和左兒子「區間 \(c\) 的最大值」的較大值。

\(x.lmx=\max(rson.lmx,lson.mxd)\)

\(lson.mnb\) 小於 \(rson.mnb\) 的情況同理。

考慮 \(lson.mnb\) 等於 \(rson.mnb\) 時,此時左右區間內都有廣義好序列,且左區間最右側和右區間最左側可以拼成一個廣義好序列(或者為空)。

所以 \(x.num=lson.num+rson.num+\max(lson.rmx,rson.lmx)\)

\(x.lmx=lson.lmx,x.rmx=ron.rmx\) 也很顯然了。

因此這樣做是正確且可行的,時間複雜度 \(O(n\log n)\)

Code

#include<bits/stdc++.h>
#define Mx 200000
using namespace std;
int n,q;
int a[200002];
set<int> s[200002];
struct aaa
{
	int laz,mnb,mxc,num,lmx,rmx;
}arr[800002];
inline int lson(int x)
{
	return (x<<1);
}
inline int rson(int x)
{
	return ((x<<1)|1);
}
inline void pushdown(int k)
{
	int ls=lson(k),rs=rson(k);
	arr[ls].mnb+=arr[k].laz,arr[rs].mnb+=arr[k].laz,arr[ls].laz+=arr[k].laz,arr[rs].laz+=arr[k].laz,arr[k].laz=0;
}
inline void pushup(int k)
{
	int ls=lson(k),rs=rson(k);
	arr[k].mnb=min(arr[ls].mnb,arr[rs].mnb),arr[k].mxc=max(arr[ls].mxc,arr[rs].mxc);
	if(arr[ls].mnb<arr[rs].mnb)arr[k].num=arr[ls].num,arr[k].lmx=arr[ls].lmx,arr[k].rmx=max(arr[ls].rmx,arr[rs].mxc);
	else if(arr[ls].mnb>arr[rs].mnb)arr[k].num=arr[rs].num,arr[k].lmx=max(arr[ls].mxc,arr[rs].lmx),arr[k].rmx=arr[rs].rmx;
	else arr[k].num=arr[ls].num+arr[rs].num+max(arr[ls].rmx,arr[rs].lmx),arr[k].lmx=arr[ls].lmx,arr[k].rmx=arr[rs].rmx;
}
inline void modifyB(int k,int l,int r,int l1,int r1,int d)
{
	if(l1>r1)return ;
	if(l>=l1 && r<=r1){arr[k].mnb+=d,arr[k].laz+=d;return ;}
	int mid=((l+r)>>1);pushdown(k);
	if(r1<=mid)modifyB(lson(k),l,mid,l1,r1,d);
	else if(l1>mid)modifyB(rson(k),mid+1,r,l1,r1,d);
	else modifyB(lson(k),l,mid,l1,mid,d),modifyB(rson(k),mid+1,r,mid+1,r1,d);
	pushup(k);
}
inline void modifyD(int k,int l,int r,int x,int d)
{
	if(l==r){arr[k].mxc=arr[k].lmx=d;return ;}
	int mid=((l+r)>>1);pushdown(k);
	if(x<=mid)modifyD(lson(k),l,mid,x,d);
	else modifyD(rson(k),mid+1,r,x,d);
	pushup(k);
}
inline void ins(int x)
{
	if(!s[a[x]].empty())modifyB(1,1,n,*s[a[x]].begin(),*s[a[x]].rbegin()-1,-1),modifyD(1,1,n,*s[a[x]].begin(),0);
	s[a[x]].insert(x),modifyD(1,1,n,*s[a[x]].begin(),s[a[x]].size()),modifyB(1,1,n,*s[a[x]].begin(),*s[a[x]].rbegin()-1,1);
}
inline void del(int x)
{
	modifyB(1,1,n,*s[a[x]].begin(),*s[a[x]].rbegin()-1,-1),modifyD(1,1,n,*s[a[x]].begin(),0),s[a[x]].erase(x);
	if(!s[a[x]].empty())modifyD(1,1,n,*s[a[x]].begin(),s[a[x]].size()),modifyB(1,1,n,*s[a[x]].begin(),*s[a[x]].rbegin()-1,1);
}
inline void query()
{
	printf("%d\n",n-arr[1].num-arr[1].lmx-arr[1].rmx);
}
int main()
{
	scanf("%d%d",&n,&q);for(int i=1;i<=n;++i)scanf("%d",&a[i]),ins(i);query();
	for(int x,y,z;q--;)scanf("%d%d",&x,&y),z=a[x],del(x),a[x]=y,ins(x),query();
	return 0;
}

細節

  1. 取 set 最後一個元素用 rbegin 而不是 end。

  2. 取 set 元素要注意 set 為空時特判。

參考文獻

  1. 「找性質+線段樹(兔隊線段樹的應用)」[Codeforces1209G2] Into Blocks (hard version)

  2. @codeforces - 1209G2@ Into Blocks (hard version)

  3. 【JZOJ 雜題選講】CF1209G