题解 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\)。

题解 CF1209G2 Into Blocks (hard version)

由此可知,在最优解的情况下,一个长度大于 \(1\) 的好序列,最后一位的 \(c\) 一定为 \(0\),否则由 \(c\) 的定义就可将该好序列分成两个好序列,矛盾。

因此,一个长度大于 \(1\) 的好序列内出现最多的元素的出现次数即为 \([l_k,r_k)\) 内 \(c\) 的最大值。

由于存在修改,此时我们不能只是简单地维护每个 \(b\) 中「连续的非零区间以及紧随其后的 \(1\) 个 \(0\)」。

我们将上面的 \(0\) 都替换为区间内最小值,即维护每个 \(b\) 中「连续的大于区间最小值的区间以及紧随其后的 \(1\) 个最小值」,我们定义这样的区间为「广义好序列」。

记一个广义好序列左侧(不在序列里)与右侧(在序列里)的最小值分别为该好序列的左右端点。

题解 CF1209G2 Into Blocks (hard version)

我们在线段树上每个节点需要维护下列 \(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

上一篇:wxWidgets初学者导引(1)——前言


下一篇:全面认识micro:bit