@description@

定义一个序列是好的,当且仅当这个序列中,相等的两个数之间的所有数全部相等。

每次操作可以将某个元素值对应的所有元素修改成另一元素值。

一个序列的困难度定义为,将这个序列修改成好的序列的最少需要修改的位置数。

现在给定初始序列 a1, a2, ..., an 以及 q 次操作,每次操作为 i x,表示将第 i 个元素修改为 x。

计算初始时以及每次操作后序列的困难度。

Input

第一行包含两个整数 n 与 q (1≤n≤200000, 0≤q≤200000),表示序列长度与操作数。

第二行包含 n 个整数 a1, a2, ..., an (1≤ai≤200000),表示初始序列。

接下来 q 行每行两个整数 it, xt (1≤it≤n, 1≤xt≤200000),描述了一次操作。

Output

输出 q + 1 个整数,表示初始序列以及每次操作后的序列的困难度。

Example

input

5 6

1 2 1 2 1

2 1

4 1

5 3

2 3

4 2

2 1

output

2

1

0

0

2

3

0

@solution@

假如如果没有修改(即 easy 版本),应该怎么做?

首先假如某个元素值 k,它最左边出现的位置为 l[k],最右边出现的位置为 r[k],则 l[k]~r[k] 必须要修改为同一元素。

假如最后 a[p]~a[q] 必须为同一元素,则我们保留 a[p]~a[q] 中出现次数最多的元素,修改掉其他的元素是最优的。

我们用数值来描述限制:定义 b[i] 表示 a[i] 与 a[i+1] 被多少次要求为同一元素。则对于每一个 k,我们将 b[ l[k] ... r[k]-1 ] 区间 + 1,即可维护 b 的值。

每次 b 中连续的非零值就形成了最后要求是同一元素的区间。

我们只需要维护出这些非零值形成的连续区间中,区间的长度 - 区间中元素出现次数最大值,这个值之和,即可得到答案。

我们将某一元素 k 出现的次数 cnt[k] 维护在 l[k] 这一位置,记作 c[l[k]]。再定义 d[i] 表示 max{c[i], c[i+1]}。

则只需要维护非零值形成的连续区间中,区间的长度 - 区间中 d 的最大值,该值之和即可。

给 b 序列区间 + 1 可以使用线段树实现。考虑使用线段树怎么实现查询功能。

我们再加入两个哨兵结点 b[0] 与 b[n+1],方便下面的实现。

因为非零值的相关数值不好维护,我们在线段树中维护的是 “删掉当前区间内所有的 b[i] 最小值后,剩下的连续区间的信息”。

为了方便合并,我们统计时并不把包含区间端点的区间统计入当前线段树结点。

因为哨兵节点的存在,我们最终线段树的根一定为我们需要的答案。

在线段树中每个结点先维护 tg, cmn, mx 这几个值,分别表示加法标记,当前区间的 b[i] 最小值,当前区间的 d[i] 最大值。

为了方便合并,再维护 lmx, rmx,表示删掉所有最小值后,左/右端点所在连续区间的最大值(注意可能不存在这样的区间,这时候记作 -1)。

最后,还要维护 ans, cnt,表示删掉所有最小值后, 区间 d 最大值之和与区间长度之和。

这里的“区间长度”,对于完整的区间(即不含区间端点)定义为 b 序列中的区间长度 + 1(即对应的原序列 a 中的区间长度);否则如果包含区间端点,定义为 b 序列中的区间长度本身。

维护过程,只需要讨论左右儿子的 cmn 是否等于当前结点的 cmn,进行分类讨论。

具体细节可以参考代码。

总时间复杂度 O(nlogn)。

@accepted code@

  1. #include<set>
  2. #include<cstdio>
  3. #include<algorithm>
  4. using namespace std;
  5. const int MAXN = 200000;
  6. int arr[MAXN + 5];
  7. struct segtree{
  8. #define lch (x<<1)
  9. #define rch (x<<1|1)
  10. struct node{
  11. int l, r;
  12. int tg, cmn;
  13. int lmx, rmx, mx, ans, cnt;
  14. }t[4*MAXN + 5];
  15. void pushup(int x) {
  16. t[x].mx = max(t[lch].mx, t[rch].mx);
  17. t[x].cmn = min(t[lch].cmn, t[rch].cmn);
  18. if( t[x].cmn == t[lch].cmn )
  19. t[x].lmx = t[lch].lmx;
  20. else t[x].lmx = max(t[lch].mx, t[rch].lmx);
  21. if( t[x].cmn == t[rch].cmn )
  22. t[x].rmx = t[rch].rmx;
  23. else t[x].rmx = max(t[rch].mx, t[lch].rmx);
  24. if( t[x].cmn != t[rch].cmn ) {
  25. t[x].ans = t[lch].ans;
  26. t[x].cnt = t[lch].cnt + t[rch].r - t[rch].l + 1;
  27. }
  28. else if( t[x].cmn != t[lch].cmn ) {
  29. t[x].ans = t[rch].ans;
  30. t[x].cnt = t[rch].cnt + t[lch].r - t[lch].l + 1;
  31. }
  32. else {
  33. t[x].ans = t[lch].ans + t[rch].ans;
  34. t[x].cnt = t[lch].cnt + t[rch].cnt;
  35. if( t[lch].rmx != -1 || t[rch].lmx != -1 )
  36. t[x].ans += max(t[lch].rmx, t[rch].lmx), t[x].cnt++;
  37. }
  38. }
  39. void pushdown(int x) {
  40. if( t[x].tg ) {
  41. t[lch].tg += t[x].tg, t[rch].tg += t[x].tg;
  42. t[lch].cmn += t[x].tg, t[rch].cmn += t[x].tg;
  43. t[x].tg = 0;
  44. }
  45. }
  46. void build(int x, int l, int r) {
  47. t[x].l = l, t[x].r = r, t[x].tg = 0, t[x].ans = 0;
  48. if( l == r ) {
  49. t[x].cmn = 0, t[x].lmx = t[x].rmx = -1, t[x].mx = max(arr[l], arr[l + 1]);
  50. return ;
  51. }
  52. int mid = (l + r) >> 1;
  53. build(lch, l, mid), build(rch, mid + 1, r);
  54. pushup(x);
  55. }
  56. void update(int x, int p) {
  57. if( t[x].l > p || t[x].r < p )
  58. return ;
  59. if( t[x].l == t[x].r ) {
  60. t[x].mx = max(arr[p], arr[p + 1]);
  61. return ;
  62. }
  63. pushdown(x);
  64. update(lch, p);
  65. update(rch, p);
  66. pushup(x);
  67. }
  68. void modify(int x, int l, int r, int d) {
  69. if( l <= t[x].l && t[x].r <= r ) {
  70. t[x].tg += d, t[x].cmn += d;
  71. return ;
  72. }
  73. if( l > t[x].r || r < t[x].l )
  74. return ;
  75. pushdown(x);
  76. modify(lch, l, r, d);
  77. modify(rch, l, r, d);
  78. pushup(x);
  79. }
  80. }T;
  81. set<int>st[MAXN + 5];
  82. int a[MAXN + 5], n, q;
  83. void update(int p) {T.update(1, p - 1), T.update(1, p);}
  84. void remove(int x) {
  85. int l = *st[a[x]].begin(), r = *st[a[x]].rbegin();
  86. T.modify(1, l, r - 1, -1), arr[l] = 0, update(l);
  87. st[a[x]].erase(x);
  88. if( !st[a[x]].empty() ) {
  89. int l = *st[a[x]].begin(), r = *st[a[x]].rbegin();
  90. T.modify(1, l, r - 1, 1), arr[l] = st[a[x]].size(), update(l);
  91. }
  92. }
  93. void add(int x) {
  94. if( !st[a[x]].empty() ) {
  95. int l = *st[a[x]].begin(), r = *st[a[x]].rbegin();
  96. T.modify(1, l, r - 1, -1), arr[l] = 0, update(l);
  97. }
  98. st[a[x]].insert(x);
  99. int l = *st[a[x]].begin(), r = *st[a[x]].rbegin();
  100. T.modify(1, l, r - 1, 1), arr[l] = st[a[x]].size(), update(l);
  101. }
  102. int main() {
  103. scanf("%d%d", &n, &q);
  104. for(int i=1;i<=n;i++)
  105. scanf("%d", &a[i]);
  106. T.build(1, 0, n);
  107. for(int i=1;i<=n;i++) add(i);
  108. printf("%d\n", T.t[1].cnt - T.t[1].ans);
  109. for(int i=1;i<=q;i++) {
  110. int it, xt; scanf("%d%d", &it, &xt);
  111. remove(it), a[it] = xt, add(it);
  112. printf("%d\n", T.t[1].cnt - T.t[1].ans);
  113. }
  114. }

@details@

好神奇的线段树。。。

@codeforces - 1209G2@ Into Blocks (hard version)的更多相关文章

  1. Into Blocks (easy version)

    G1 - Into Blocks (easy version) 参考:Codeforces Round #584 - Dasha Code Championship - Elimination Rou ...

  2. Codeforces Round #584 - Dasha Code Championship - Elimination Round (rated, open for everyone, Div. 1 + Div. 2) G1. Into Blocks (easy version)

    题目:https://codeforc.es/contest/1209/problem/G1 题意:给你一个序列,要你进行一些操作后把他变成一个好序列,好序列的定义是,两个相同的数中间的数都要与他相同 ...

  3. Codeforces 838A - Binary Blocks(二维前缀和+容斥)

    838A - Binary Blocks 思路:求一下前缀和,然后就能很快算出每一小正方块中1的个数了,0的个数等于k*k减去1的个数,两个的最小值就是要加进答案的值. 代码: #include< ...

  4. CodeForces 1118F2. Tree Cutting (Hard Version)

    题目简述:给定$n \leq 3 \times 10^5$个节点的树,其中一部分节点被染色,一共有$k$种不同的颜色.求将树划分成 $k$ 个不相交的部分的方案数,使得每个部分中除了未染色的节点以外的 ...

  5. codeforces#1290E2 - Rotate Columns (hard version)(子集dp)

    题目链接: https://codeforces.com/contest/1209/problem/E2 题意: 给出$n$行和$m$列 每次操作循环挪动某列一次 可以执行无数次这样的操作 让每行最大 ...

  6. codeforces#1165 F2. Microtransactions (hard version) (二分+贪心)

    题目链接: https://codeforces.com/contest/1165/problem/F2 题意: 需要买$n$种物品,每种物品$k_i$个,每个物品需要两个硬币 每天获得一个硬币 有$ ...

  7. Codeforces 1304F2 Animal Observation (hard version) 代码(dp滑动窗口线段树区间更新优化)

    https://codeforces.com/contest/1304/problem/F2 #include<bits/stdc++.h> using namespace std; ; ...

  8. Codeforces 1326F2 - Wise Men (Hard Version)(FWT+整数划分)

    Codeforces 题目传送门 & 洛谷题目传送门 qwq 这题大约是二十来天前 AC 的罢,为何拖到此时才完成这篇题解,由此可见我是个名副其实的大鸽子( 这是我上 M 的那场我没切掉的 F ...

  9. Codeforces 1446D2 - Frequency Problem (Hard Version)(根分)

    Codeforces 题面传送门 & 洛谷题面传送门 人菜结论题做不动/kk 首先考虑此题一个非常关键的结论:我们设整个数列的众数为 \(G\),那么在最优子段中,\(G\) 一定是该子段的众 ...

随机推荐

  1. io.spring.platform继承方式和import方式更改依赖版本号的问题

    使用io.spring.platform时,它会管理各类经过集成测试的依赖版本号. 但有的时候,我们想使用指定的版本号,这个时候就需要去覆盖io.spring.platform的版本号. 前面的文章总 ...

  2. SVG 动态添加元素与事件

    SVG文件是由各个元素组成.元素由标签定义,而标签格式即html的元素定义格式.但是载入一个SVG文件,却无法通过常规的js获取对象方式来获取到SVG中定义的元素,更无法通过这种方式来动态添加SVG元 ...

  3. php百度地图计算两地现实距离

    请自行到百度地图官网申请您的ak <!--前端获取手机经纬度的代码--> <!--<!DOCTYPE html>--> <!--<html lang=& ...

  4. day18 17.c3p0连接池使用

    连接池时间长不用空闲着,dbcp是不回收的,性能可能有些问题.c3p0是可以自动回收.实际开发中c3p的生产力比dbcp强,性能上更强. package cn.itcast.datasource; i ...

  5. Spring2.5依靠注入的方式有三种

    Spring2.5依靠注入的方式有三种: 1.通过setter方法注入: 2.通过构造方法注入: 3.通过注解进行注入: 第一种方式:通过setter方法注入 Java代码 package com.t ...

  6. Leetcode40. Combination Sum组合总数2 II

    给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. ...

  7. 【笔记】LR配置ODBC连接数据库进行参数化(mysql )未完待续

    很多时候我们需要大量的参数数据,但是光光靠手填写是非常麻烦的,既然被测对象的数据都在数据库,那么我们直接读取数据库回来就轻松简便很多. data  wizard 提供了一个从ODBC的连接获得数据转化 ...

  8. Android学习笔记之mainfest文件中android属性

    android:allowTaskReparenting 是否允许activity更换从属的任务,比如从短信息任务 切换到浏览器任务. -------------------------------- ...

  9. Sql基本知识回顾

    一. SQL 基本语句 SQL 分类: DDL —数据定义语言 (Create , Alter , Drop , DECLARE) DML —数据操纵语言 (Select , Delete , Upd ...

  10. tcpdump抓取udp报文

    使用tcpdump命令抓取UDP 2000端口报文,并将报文保存到当前目录下的udp.cap文件,命令如下: tcpdump -i 网络接口名称 udp port 2000 -w ./udp.cap ...