CF1209G2 Into Blocks (hard version)

看题解过了这道题,理解的时候比较费力,写一篇题解加深印象。

首先考虑 easy vesion,将最少代价转化成最多能留下来多少个,一个区间里能留下来的最大数就是众数个数,显然相等的数要放在一起考虑,容易发现两个区间的众数个数一定大于等于将这两个区间合并后的众数个数,所以按照这样拆成很多个小区间就行了。

code

#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#pragma GCC optimeze(3)
#pragma GCC optimeze(2)
#define PII pair<int, int>
#define pb push_back
#define fi first
#define se second
#define lowbit(x) (x & (-x))
using namespace std;
const int N=2e5+10;
const int M=5e3+5; 
const int mod=1e9+7; 
double eps=1e-9;
int n,q,a[N],bj[N],maxx,pos[N],ans,x;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++)cin>>a[i],bj[a[i]]++,pos[a[i]]=i;
	for(int i=1;i<=n;){
		x=i,maxx=0;
		while(i<=x){
			maxx=max(maxx,bj[a[i]]),x=max(x,pos[a[i]]),i++;
		}
		ans+=maxx;
	}
	cout<<n-ans;
	return 0;
}

然后再来考虑带修改的,因为我们分出区间的时候是一直看区间内是否有数没有将与它相等的最后一个数覆盖到,所以有一个 trick:将一个数 \(i\) 的第一个出现的位置设为 \(l_i\) 最后出现的位置设为 \(r_i\) 另开一个数组 \(b\),将 \(b\) 数组上的 \([l_i,r_i)\) 加一,那么如果 \(b_j\) 为 0,那么 \(j\) 这个位置一定是一段区间的结束位置,可以自己模拟一下样例,可以发现这个是对的。

那么现在这个序列就被 \(0\) 分割成了很多个区间,现在要维护的就是每个区间内的众数出现次数之和。

我们可以再开一个数组 \(c\)\(i\) 的出现次数放到 \(c_{l_i}\) 里面,然后就变成了很多个区间内的最大值之和,可以用线段树维护,考虑要维护些什么东西:

\(b\) 数组在区间内的最小值 \(minn\),这个是为了看这个区间内是否有 \(0\)

\(c\) 数组在区间内的最大值 \(maxx\)

这个区间的最左边和最右边的两个被分割出来的区间不一定是完整的区间,可能会和旁边的区间合并比如:

\(\alpha \ \alpha\ \beta\) \(|\) $ \beta\ \beta\ \zeta$

(不同的字母表示被 \(0\) 分割出来不同的区间,被 \(|\) 隔开的分别是线段树上两个节点维护的区间)可以看到,左边这个区间的最右边被分割出来的区间和右边这个区间最左边被分割出来的区间在 \(b\) 数组内属于被分割出来的同一个区间,还能合并。

所以记录这个区间里面被分割出来的最左边区间和最右边区间各自的 \(c\) 数组的最大值 \(lv\)\(rv\)

最后要统计答案,所以还要维护这个区间内被分割出来的区间的最大值之和 \(sum\),因为最左边的区间和最右边的区间还不确定,所以先不计入这里面。

然后考虑 pushup,\(lv\)\(rv\)\(sum\) 要分类讨论

  1. 左儿子的区间有 \(0\),右儿子的区间没有 \(0\)

这个时候 \(lv\) 就是左儿子的 \(lv\),而左儿子最右边被分割出来的区间和右儿子属于同一个被分割出来的区间,所以 \(rv\) 是左儿子的 \(rv\) 和右儿子的 \(maxx\) 的最大值,\(sum\) 就是左儿子的 \(sum\)

  1. 右儿子的区间有 \(0\),左儿子的区间没有 \(0\)

和上面的的情况同理。

  1. 左儿子和右儿子的区间均有 \(0\)

这个时候 \(lv\) 等于左儿子的 \(lv\)\(rv\) 等于右儿子的 \(rv\)\(sum\) 等于左儿子的 \(sum\) 加右儿子的 \(sum\) 加左儿子的 \(rv\) 和右儿子的 \(lv\) 的最大值(这里就是合并两个区间)。

修改只用维护区间修改 \(b\) 数组和单点修改 \(c\) 数组 然后这颗线段树就维护好了,查询答案就是 \((n-sum_1-lv_1-rv_1)\)

code

#include <bits/stdc++.h>
//#define int long long
#define ull unsigned long long
#pragma GCC optimeze(3)
#pragma GCC optimeze(2)
#define PII pair<int, int>
#define pb push_back
#define fi first
#define se second
#define lowbit(x) (x & (-x))
using namespace std;
const int N=2e5+10;
const int M=17; 
const int mod=1e9+7; 
double eps=1e-9;
struct node{
	int maxx,sum,lv,rv,minn,tag;
}tr[N<<2];
int n,a[N],q;
set<int>s[N];
void pushdown(int p){
	if(tr[p].tag){
		tr[p<<1].minn+=tr[p].tag,tr[p<<1].tag+=tr[p].tag;
		tr[p<<1|1].minn+=tr[p].tag,tr[p<<1|1].tag+=tr[p].tag;
		tr[p].tag=0;
	}
}
void pushup(int p){
	tr[p].minn=min(tr[p<<1].minn,tr[p<<1|1].minn);
	tr[p].maxx=max(tr[p<<1].maxx,tr[p<<1|1].maxx);
	if(tr[p<<1].minn<tr[p<<1|1].minn){
		tr[p].lv=tr[p<<1].lv,tr[p].sum=tr[p<<1].sum,tr[p].rv=max(tr[p<<1|1].maxx,tr[p<<1].rv);
	}
	if(tr[p<<1].minn>tr[p<<1|1].minn){
		tr[p].rv=tr[p<<1|1].rv,tr[p].sum=tr[p<<1|1].sum,tr[p].lv=max(tr[p<<1].maxx,tr[p<<1|1].lv);
	}
	if(tr[p<<1].minn==tr[p<<1|1].minn){
		tr[p].lv=tr[p<<1].lv,tr[p].rv=tr[p<<1|1].rv,tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum+max(tr[p<<1].rv,tr[p<<1|1].lv);
	}
}
void add1(int l,int r,int x,int p,int s,int t){
	if(l>r)return;
	if(s>=l&&t<=r){
		tr[p].minn+=x,tr[p].tag+=x;
		return;
	}
	int mid=(s+t)>>1;
	pushdown(p);
	if(mid>=l)add1(l,r,x,p<<1,s,mid);
	if(mid<r)add1(l,r,x,p<<1|1,mid+1,t);
	pushup(p);
}
void add2(int x,int q,int p,int s,int t){
	if(s==t){
		tr[p].maxx+=q,tr[p].lv+=q;
		return;
	}
	pushdown(p);
	int mid=(s+t)>>1;
	if(mid>=x)add2(x,q,p<<1,s,mid);
	else add2(x,q,p<<1|1,mid+1,t);
	pushup(p);
}
void update(int co,int k){
	int siz=s[co].size();
	if(!siz)return;
	add1((*s[co].begin()),*(--s[co].end())-1,k,1,1,n);
	add2(*s[co].begin(),k*siz,1,1,n);
}
int ask(){
	return n-tr[1].sum-tr[1].lv-tr[1].rv;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;int mx=0;
	for(int i=1;i<=n;i++){
		cin>>a[i],s[a[i]].insert(i),mx=max(mx,a[i]);
	}
	for(int i=1;i<=mx;i++){if(s[i].size())update(i,1);}
	cout<<ask()<<'\n';
	while(q--){
		int x,y;cin>>x>>y;
		int col=a[x];
		update(col,-1),s[col].erase(x),update(col,1);
		update(y,-1),s[y].insert(x),update(y,1);
		a[x]=y;
		cout<<ask()<<'\n'; 
	}
	return 0;
}
posted @ 2025-02-10 16:23  Xdik  阅读(12)  评论(0)    收藏  举报