羽未「umi」

前言

首先这道题要把题读对,而且不能在考试还剩 10 分钟的时候才读对。😦

老子看到二刺猿就烦,能不能下次不要搞这些莫名其妙的背景!!

题目

一开始有一个长度为 \(n\) 的序列,每个位置都有一个颜色 \(a_i\),你可以进行若干次操作:

  • 选择一种颜色,将这种颜色全部变为另外一种颜色。

目标状态是相同的颜色是序列中连续的一段。

但这还不够,所以有 \(q\) 次修改,每次修改会更改一个位置上的颜色。

你需要对初始序列和每个修改之后都回答最小改变量,注意改变量不是操作次数,而是操作后的序列和原序列的不同元素个数。

你的操作不会保留到下一次修改。

\(1\le n,a_i\le 2\times 10^5;0\le q\le 2\times 10^5.\)

讲解

首先暴力怎么做?考虑有区间有交集的颜色一定会变成一种颜色,在这些颜色中选出最多的一个不变,其它的都变成这种颜色,线性扫可以做到 \(O(cq)\) (c是颜色数量)。

我们如何加速这一过程?太阳神给出了一条从未设想也想都不敢想的道路:线段树!线段树真的无所不能!

  1. 维护每种颜色的区间,用set随便做。

  2. 一个转化,将颜色的数量放在颜色区间的左端点,将每段最多的颜色转化为区间最大值!

  3. 一个 trick,记 \(b_i\) 为有多少个区间包含了 \(i\)\(i+1\),问题转化为求 \(\{b_i\}\) 两个 \(0\) 之间的最大值之和。

  4. observation,\(b_i \ge 0\),所以问题再转化为 \(\{b_i\}\) 的最小值之间的最大值之和。

现在已经可以做了,修改时只需要对 \(b_i\) 进行区间加减,对颜色数量进行单点修改,魔幻的是上述这些信息都可以正常合并!

时间复杂度 \(O((n+q)\log_2n)\).

代码

米奇妙妙屋
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 200005;
const int INF = 0x3f3f3f3f;
int n,m;
int a[MAXN];

LL Read()
{
	LL x = 0,f = 1; char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10)+(c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

set<int> s[MAXN];

#define lc (x<<1)
#define rc (x<<1|1)
int val[MAXN];
struct node
{
	int MIN,lz,s,lm,rm,MAX;
}t[MAXN<<2];
void cc(int x,int v){t[x].MIN += v; t[x].lz += v;}
void up(int x)
{
	t[x].MIN = Min(t[lc].MIN,t[rc].MIN);
	t[x].MAX = Max(t[lc].MAX,t[rc].MAX);
	if(t[lc].MIN < t[rc].MIN)
	{
		t[x].lm = t[lc].lm; t[x].s = t[lc].s;
		t[x].rm = Max(t[lc].rm,t[rc].MAX);
	}
	else if(t[lc].MIN > t[rc].MIN)
	{
		t[x].rm = t[rc].rm; t[x].s = t[rc].s;
		t[x].lm = Max(t[rc].lm,t[lc].MAX);
	}
	else
	{
		t[x].rm = t[rc].rm; t[x].lm = t[lc].lm;
		t[x].s = t[lc].s + t[rc].s + Max(t[lc].rm,t[rc].lm);
	}
}
void down(int x){if(!t[x].lz) return;cc(lc,t[x].lz); cc(rc,t[x].lz); t[x].lz = 0;}
void Add(int x,int l,int r,int ql,int qr,int v)
{
	if(ql > qr) return;
	if(ql <= l && r <= qr){cc(x,v);return;}
	int mid = (l+r) >> 1;
	down(x);
	if(ql <= mid) Add(lc,l,mid,ql,qr,v);
	if(mid+1 <= qr) Add(rc,mid+1,r,ql,qr,v);
	up(x); 
}
void Change(int x,int l,int r,int pos)
{
	if(l == r)
	{
		t[x].lm = t[x].rm = t[x].MAX = val[pos];
		return;
	}
	int mid = (l+r) >> 1;
	down(x);
	if(pos <= mid) Change(lc,l,mid,pos);
	else Change(rc,mid+1,r,pos);
	up(x);
}

void clr(int x)
{
	if(s[x].empty()) return;
	int l = *s[x].begin(),r = *s[x].rbegin()-1;
	val[l] = 0; Add(1,0,n,l,r,-1);
	Change(1,0,n,l);
}
void calc(int x)
{
	if(s[x].empty()) return;
	int l = *s[x].begin(),r = *s[x].rbegin()-1;
	val[l] = s[x].size(); Add(1,0,n,l,r,1);
	Change(1,0,n,l);
}

int main()
{
	freopen("umi.in","r",stdin);
	freopen("umi.out","w",stdout);
	n = Read(); m = Read(); 
	for(int i = 1;i <= n;++ i) a[i] = Read(),clr(a[i]),s[a[i]].insert(i),calc(a[i]);
	Put(n-t[1].s,'\n');
	while(m --> 0)
	{
		int pos = Read(),color = Read();
		clr(a[pos]); s[a[pos]].erase(s[a[pos]].lower_bound(pos)); calc(a[pos]);
		clr(color); s[a[pos] = color].insert(pos); calc(color);
		Put(n-t[1].s,'\n');
	}
	return 0;
}

总结

区间问题大胆猜想用线段树,说不定真的能做!

这道题学到了将序列分隔之后对所有区间求值的 trick.

posted @ 2021-11-18 08:45  皮皮刘  阅读(67)  评论(0编辑  收藏  举报