【CF1209G】Into Blocks (easy+hard) 题解

题目大意

  一个序列是好的,当且仅当,若两个元素相等,则它们之间的所有元素都相等,比如 [ 3 , 3 , 3 , 4 , 1 , 1 ] [3,3,3,4,1,1] [3,3,3,4,1,1]
  现在有一个初始序列 a 1 , ⋯   , a n a_1,\cdots,a_n a1,,an,你要把它修改成好的。如果你把一个值为 x x x 的元素改成 y y y,那么所有值为 x x x 的元素都要改成 y y y。求最少需要修改多少个位置。
  在 hard version 中,还有 q q q 次单点修改,每次修改都要回答一次。(在 easy version 中, q = 0 q=0 q=0

   1 ≤ n , a i ≤ 2 × 1 0 5 ,   0 ≤ q ≤ 2 × 1 0 5 1 \leq n,a_i \leq 2 \times 10^5,~0 \leq q \leq 2 \times 10^5 1n,ai2×105, 0q2×105
  5s

\\
\\
\\

easy version

  设数字 x x x 最早出现在 l l l,最晚出现在 r r r,那么最终 [ l , r ] [l,r] [l,r] 全部都要相同。
  那么每个数字相当于给出一个区间,这些区间形成了若干个不相交的区间并,每个区间并内全部要相同。
  那么最优的肯定是每个区间并内选择众数保留。答案就是 n n n 减去各众数的出现次数和。

  用个栈+并查集就可以求出来了。

hard version

  感谢大佬的博客让我读懂了题解 qaq

  现在是要想办法维护这个区间并,以及区间并内的众数。

  一个非常好的办法是,对于数字 x x x,假设它的出现区间是 [ l , r ] [l,r] [l,r],给 [ l , r − 1 ] [l,r-1] [l,r1] 全体加 1 1 1,那么 0 0 0 就是区间并的分界点。

  因此用一个线段树维护区间并的情况。每个区间记录最小值、最小值的最左位置 l l l、最小值的最右位置 r r r [ l + 1 , r ] [l+1,r] [l+1,r] 的各众数的出现次数和 s u m sum sum(去掉开头和结尾的连续段是为了方便合并),记为 { m i n v a l , l , r , s u m } \{minval,l,r,sum\} {minval,l,r,sum}。如果最小值为 0 0 0,那么 s u m sum sum 就是要求的东西了。
  合并两个区间的话,首先是取最小值较小的那一边(因为如果最小值不同那么大的那边肯定没有 0 0 0),如果两边最小值相同,则是 { m i n v a l , l l s o n , r r s o n , s u m l s o n + s u m r s o n + s ( r l s o n + 1 , l r s o n ) } \{minval,l_{lson},r_{rson},sum_{lson}+sum_{rson}+s(r_{lson}+1,l_{rson})\} {minval,llson,rrson,sumlson+sumrson+s(rlson+1,lrson)},其中 s ( l , r ) s(l,r) s(l,r) 表示 [ l , r ] [l,r] [l,r] 的众数的出现次数。

  所以接下来的问题就是 s ( l , r ) s(l,r) s(l,r) 怎么求。普通的求区间众数是很难做的,但是这题有个性质,就是如果我问 [ l , r ] [l,r] [l,r] 的区间众数,那么这个众数肯定全部落在 [ l , r ] [l,r] [l,r] 之中,不会在区间外。因此,再开一棵线段树,在每个数 x x x 的最左位置赋值为 x x x 的出现次数,那么 s ( l , r ) s(l,r) s(l,r) 实际上就是个区间最大值。

参考

  https://blog.csdn.net/qq_39972971/article/details/100884760

代码

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

const int maxn=2e5+5;

struct TR{
	int sum,l0,r0,nmin;
};

int n,q,a[maxn],maxa;
set<int> pos[maxn];

int nmax[4*maxn];
void nmax_xg(int k,int l,int r,int x,int z)
{
	if (l==r)
	{
		nmax[k]+=z;
		return;
	}
	int t=k<<1, mid=(l+r)>>1;
	if (x<=mid) nmax_xg(t,l,mid,x,z); else nmax_xg(t+1,mid+1,r,x,z);
	nmax[k]=max(nmax[t],nmax[t+1]);
}
int nmax_cx(int k,int l,int r,int x,int y)
{
	if (x>y) return 0;
	if (x<=l && r<=y) return nmax[k];
	int t=k<<1, mid=(l+r)>>1, re=0;
	if (x<=mid) re=max(re,nmax_cx(t,l,mid,x,y));
	if (mid<y) re=max(re,nmax_cx(t+1,mid+1,r,x,y));
	return re;
}

TR tr[4*maxn];
int bz[4*maxn];
void update(int k,int t)
{
	tr[t].nmin+=bz[k], tr[t+1].nmin+=bz[k];
	bz[t]+=bz[k], bz[t+1]+=bz[k];
	bz[k]=0;
}
TR merge(const TR &a,const TR &b)
{
	if (a.nmin<b.nmin) return a;
		else if (a.nmin>b.nmin) return b;
			else return (TR){a.sum+b.sum+nmax_cx(1,1,n,a.r0+1,b.l0),a.l0,b.r0,a.nmin};
}
void tr_js(int k,int l,int r)
{
	if (l==r)
	{
		tr[k].l0=tr[k].r0=l;
		return;
	}
	int t=k<<1, mid=(l+r)>>1;
	tr_js(t,l,mid), tr_js(t+1,mid+1,r);
	tr[k]=merge(tr[t],tr[t+1]);
}
void tr_xg_sing(int k,int l,int r,int x,int z)
{
	if (l==r)
	{
		nmax_xg(1,1,n,x,z);
		return;
	}
	int t=k<<1, mid=(l+r)>>1;
	update(k,t);
	if (x<=mid) tr_xg_sing(t,l,mid,x,z); else tr_xg_sing(t+1,mid+1,r,x,z);
	tr[k]=merge(tr[t],tr[t+1]);
}
void tr_xg_q(int k,int l,int r,int x,int y,int z)
{
	if (x>y) return;
	if (x<=l && r<=y)
	{
		tr[k].nmin+=z;
		bz[k]+=z;
		return;
	}
	int t=k<<1, mid=(l+r)>>1;
	update(k,t);
	if (x<=mid) tr_xg_q(t,l,mid,x,y,z);
	if (mid<y) tr_xg_q(t+1,mid+1,r,x,y,z);
	tr[k]=merge(tr[t],tr[t+1]);
}

void modify(int x,int sig)
{
	if (pos[x].empty()) return;
	tr_xg_sing(1,0,n,*pos[x].begin(),sig*pos[x].size());
	set<int>::iterator it=pos[x].end();
	it--;
	tr_xg_q(1,0,n,*pos[x].begin(),*it-1,sig);
}

void calc_ans()
{
	int ans=n-tr[1].sum-nmax_cx(1,1,n,1,tr[1].l0)-nmax_cx(1,1,n,tr[1].r0+1,n);
	printf("%d\n",ans);
}

int main()
{
	scanf("%d %d",&n,&q);
	fo(i,1,n)
	{
		scanf("%d",&a[i]);
		maxa=max(maxa,a[i]);
		pos[a[i]].insert(i);
	}
	
	tr_js(1,0,n);
	fo(i,1,maxa) modify(i,1);
	
	calc_ans();
	while (q--)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		
		modify(a[x],-1);
		pos[a[x]].erase(x);
		modify(a[x],1);
		
		modify(y,-1);
		pos[y].insert(x);
		modify(y,1);
		a[x]=y;
		
		calc_ans();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值