题解 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;
}
细节
-
取 set 最后一个元素用 rbegin 而不是 end。
-
取 set 元素要注意 set 为空时特判。