CF1209G2 Into Blocks (hard version) 题解
思路比较困难的一道线段树题目。
考虑对于一段一段的混合颜色区间,我们的最优策略显然是留下其中元素数量最多的颜色。
比如:
12221123343
这就是两个颜色区间,一个是 \(1222112\),另一个是 \(3343\)。
其中第一个留下 \(2\),第二个留下 \(3\) 显然是最优的。
考虑利用线段树来维护这个东西。
我们考虑将每个元素维护 \(s_i,t_i\) 分别为元素的起始位置和末尾位置。
可以利用 \(\text{set}\) 来维护。
维护一个 \(b\) 数组。
对于每个颜色,就在 \(b\) 数组上对于 \([s_i,t_i)\) 范围内全部加 \(1\)。
可以发现,\(b\) 数组内为零的每一位都是一个混合颜色区间的末尾。
那么我们同时也可以在每一个颜色的起始位置上存放这个颜色的元素个数。
我们就可以维护这里面每一个区间的最大值。
使用线段树来维护。
讲一下实现细节。
维护一个 \(ls,rs,sum,maxx,minn\)。
\(ls\) 维护的是靠左的最大值。
\(rs\) 维护的是靠右的最大值。
\(sum\) 维护的是最大值之和。
\(maxx\) 维护的是整个区间的最大值之和。
\(minn\) 维护的是这个区间里面 \(b\) 数组的最小值。
可以很简单的发现,如果一个区间里面 \(b\) 数组没有零,那么就随便上传数据。
所以我们可以根据有零的区间来讨论。
-
如果 \(minn_l < minn_r\) 也就是零在左边。
那么,这个区间靠右的最大值是右区间的最大值和左区间靠右的最大值的最大值,最大值之和就是左区间的最大值之和。
-
如果 \(minn_l > minn_r\) 也就是零在右边。
那么,这个区间靠左的最大值是左区间的最大值和右区间靠左的最大值的最大值,最大值之和就是右区间的最大值之和。
-
如果 \(minn_l > minn_r\) 也就是两边都有零。
那么,最大值之和就是右区间的最大值之和和左区间的最大值之和和左区间靠右的最大值与右区间靠左的最大值中的最大值之和。
其他的就可以直接看代码了。
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n , m , a[N];
set<int> q[N];
struct Tree
{
int l , r;
int ls , rs , sum;
int minn , maxx , tag;
}t[N * 4];
inline int read()
{
int asd = 0 , qwe = 1; char zxc;
while(!isdigit(zxc = getchar())) if(zxc == '-') qwe = -1;
while(isdigit(zxc)) asd = asd * 10 + zxc - '0' , zxc = getchar();
return asd * qwe;
}
inline void build(int p , int l , int r)
{
t[p].l = l , t[p].r = r;
if(l == r) return;
build(p * 2 , l , (l + r) / 2);
build(p * 2 + 1 , (l + r) / 2 + 1 , r);
}
inline void pushDown(int p)
{
t[p * 2].minn += t[p].tag;
t[p * 2 + 1].minn += t[p].tag;
t[p * 2].tag += t[p].tag;
t[p * 2 + 1].tag += t[p].tag;
t[p].tag = 0;
}
inline void pushUp(int p)
{
int l = p * 2 , r = p * 2 + 1;
t[p].minn = min(t[l].minn , t[r].minn);
t[p].maxx = max(t[l].maxx , t[r].maxx);
if(t[l].minn < t[r].minn)
{
t[p].ls = t[l].ls;
t[p].sum = t[l].sum;
t[p].rs = max(t[l].rs , t[r].maxx);
}
if(t[l].minn > t[r].minn)
{
t[p].rs = t[r].rs;
t[p].sum = t[r].sum;
t[p].ls = max(t[r].ls , t[l].maxx);
}
if(t[l].minn == t[r].minn)
{
t[p].rs = t[r].rs;
t[p].ls = t[l].ls;
t[p].sum = t[l].sum + t[r].sum + max(t[l].rs , t[r].ls);
}
}
inline void update1(int p , int l , int r , int k)
{
if(l > r) return;
if(l <= t[p].l && t[p].r <= r)
{
t[p].minn += k;
t[p].tag += k;
return;
}
pushDown(p);
int mid = (t[p].l + t[p].r) / 2;
if(l <= mid) update1(p * 2 , l , r , k);
if(r > mid) update1(p * 2 + 1 , l , r , k);
pushUp(p);
}
inline void update2(int p , int x , int k)
{
if(t[p].l == t[p].r)
{
t[p].maxx += k;
t[p].ls += k;
return;
}
pushDown(p);
int mid = (t[p].l + t[p].r) / 2;
if(x <= mid) update2(p * 2 , x , k);
if(x > mid) update2(p * 2 + 1 , x , k);
pushUp(p);
}
inline void updateUsed(int col , int k)
{
int siz = q[col].size();
if(!siz) return;
update1(1 , *q[col].begin() , *(--q[col].end()) - 1 , k);
update2(1 , *q[col].begin() , k * siz);
}
inline int ask()
{
return n - t[1].sum - t[1].ls - t[1].rs;
}
int main()
{
int maxA = 0;
n = read() , m = read();
for(int i = 1;i <= n;i++)
a[i] = read() , q[a[i]].insert(i),
maxA = max(maxA , a[i]);
build(1 , 1 , n);
for(int i = 1;i <= maxA;i++)
if(!q[i].empty())
updateUsed(i , 1);
printf("%d\n" , ask());
for(int i = 1;i <= m;i++)
{
int x = read() , y = read();
int col = a[x];
updateUsed(col , -1) , q[col].erase(x);
updateUsed(col , 1) , updateUsed(y , -1);
q[y].insert(x) , updateUsed(y , 1) , a[x] = y;
printf("%d\n" , ask());
}
return 0;
}