题目传送门

题目大意

给出一个\(n\)个数的数列\(A_{1,2,...,n}\),求出一个单调不减的数列\(B_{1,2,...,n}\),使得\(\sum_{i=1}^{n}(A_i-B_i)^2\)最小。

有\(m\)次查询,每次将某个\(A_x\)更改为\(y\),求出修改后的答案。查询之间互相独立。

\(n,m\le 10^5\)

思路

其实这道题正解是用保序回归,但是找规律也能找出来。

我们通过观察发现,对于一段相同的\(B_i\),\(B_i\)是该段的平均值。于是我们大胆猜测,我们最优方案就是把\(A\)序列划分成一些区间,每一段的\(B\)都是\(A\)的平均值,并且\(B\)单调不降。

我们发现这顺便还可以发现一个事情:我们肯定应该多分区间,否则的话我们肯定只分一段就完事了。

于是,我们现在考虑一个区间对答案的贡献:

\[\sum_{i=l}^{r} (A_i-d)^2
\]

其中\(d\)是这段区间的平均值。

\[=\sum_{i=l}^{r} (A_i^2-2A_id+d^2)
\]
\[=\sum_{i=l}^{r} A_i^2-\dfrac{(\sum_{i=l}^{r}A_i)^2}{r-l+1}
\]

于是我们发现我们只需要维护区间平方和、区间和、区间长度。于是,我们就顺利地拿到了\(50\)分。

考虑\(100\)分。一个很显然的事情是,我们会改变的决策区间一定是\([L,R]\),至于\([1,L)\)和\((R,n]\)都不会被影响,于是我们可以预处理一下。

问题就是如何找到\(L,R\)。一个不是很显然的事情就是,我们选的端点一定都是一开始分的区间的某些端点。因为我们一个区间如果从中间分开,前一段的平均值一定比后一段的平均值大,因为如果比它小的话肯定分开更优(分得越多越优)。于是,如果我们不是选一开始的端点的话,被分开的那一段前一段一定比后一段的平均值大,与平均值单调不减矛盾,得证。

同时,我们还发现答案是具有单调性的。于是我们可以考虑先二分\(R\),然后再二分\(L\)。二分\(R\)直接二分就好了,二分\(L\)的话,我们发现并不需要考虑前面的是否满足条件(这个自己想一下就明白了),只需要考虑分开的后面的,于是这个我们可以在主席树上查询。具体见代码。

时间复杂度\(\Theta(n\log^2 n)\),空间复杂度\(\Theta(n\log n)\)。

\(\texttt{Code}\)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define Int register int
  4. #define mod 998244353
  5. #define ll long long
  6. #define MAXN 100005
  7. template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
  8. template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
  9. template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
  10. int n,m,a[MAXN],inv[MAXN];
  11. int mul (int a,int b){return 1ll * a * b % mod;}
  12. int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
  13. int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
  14. struct node{
  15. int rt,ans,top;//对应主席树的顶点、答案、栈顶编号
  16. }pre[MAXN],suf[MAXN];
  17. struct Data{
  18. int len,sqr;ll sum;//因为要比较平均值大小,所以区间和不能取模
  19. Data(){}
  20. Data(int len1,ll sum1,int sqr1){len = len1,sum = sum1,sqr = sqr1;}
  21. Data operator + (Data p){return Data(len + p.len,sum + p.sum,add (sqr,p.sqr));}
  22. Data operator - (Data p){return Data(len - p.len,sum - p.sum,dec (sqr,p.sqr));}
  23. bool operator < (Data p)const{return p.len ? (len ? 1.0 * sum / len < 1.0 * p.sum / p.len: 1) : 0;}
  24. bool operator <= (Data &p)const{return p.len ? (len ? 1.0 * sum / len <= 1.0 * p.sum / p.len: 1) : 0;}
  25. int calc (){return dec (sqr,mul (mul (sum % mod,sum % mod),inv[len]));}
  26. }sum[MAXN],sta[MAXN];
  27. Data calc (int l,int r){return sum[r] - sum[l - 1];}
  28. struct Segment{
  29. int cnt;
  30. struct Node{
  31. int son[2],l,r,k;//k是指l->r这写区间第一段区间的右端点
  32. }tree[MAXN * 60];
  33. void Pushup (int x){
  34. tree[x].l = tree[tree[x].son[0]].l,tree[x].r = tree[tree[x].son[tree[x].son[1] > 0]].r;
  35. tree[x].k = tree[tree[x].son[0]].k;
  36. }
  37. void modify (int &x,int l,int r,int pos,int L,int R){
  38. tree[++ cnt] = tree[x],x = cnt;
  39. if (l == r) return tree[x].l = L,tree[x].r = tree[x].k = R,void ();
  40. int mid = (l + r) >> 1;
  41. if (pos <= mid) modify (tree[x].son[0],l,mid,pos,L,R);
  42. else modify (tree[x].son[1],mid + 1,r,pos,L,R);
  43. Pushup (x);
  44. }
  45. int queryr (int x,int l,int r,int pos){//查找第pos段区间的右端点
  46. if (l == r) return tree[x].r;
  47. int mid = (l + r) >> 1;
  48. if (pos <= mid) return queryr (tree[x].son[0],l,mid,pos);
  49. else return queryr (tree[x].son[1],mid + 1,r,pos);
  50. }
  51. int queryl (int x,int l,int r,int pos,Data &tmp){//找到最靠右的满足的L,并对答案进行合并
  52. if (r <= pos){
  53. Data Lget = calc (tree[x].l,tree[x].k),Rget = calc (tree[x].k + 1,tree[x].r);
  54. if (Rget + tmp <= Lget) return tmp = tmp + calc (tree[x].l,tree[x].r),0;
  55. if (l == r) return tree[x].r;
  56. }
  57. int res,mid = (l + r) >> 1;
  58. if (pos > mid && (res = queryl (tree[x].son[1],mid + 1,r,pos,tmp))) return res;
  59. else return queryl (tree[x].son[0],l,mid,pos,tmp);
  60. }
  61. }Tree;
  62. void Init(){
  63. for (Int i = 1,top = 0;i <= n;++ i){
  64. sta[++ top] = Data (1,a[i],mul (a[i],a[i]));
  65. while (top > 1 && sta[top] <= sta[top - 1]) sta[top - 1] = sta[top - 1] + sta[top],-- top;
  66. pre[i].ans = add (pre[i - sta[top].len].ans,sta[top].calc());//计算答案
  67. pre[i].rt = pre[i - 1].rt,pre[i].top = top;
  68. Tree.modify (pre[i].rt,1,n,top,i - sta[top].len + 1,i);
  69. }
  70. for (Int i = n,top = 0;i;-- i){
  71. sta[++ top] = Data (1,a[i],mul (a[i],a[i]));
  72. while (top > 1 && sta[top - 1] <= sta[top]) sta[top - 1] = sta[top - 1] + sta[top],-- top;
  73. suf[i].ans = add (suf[i + sta[top].len].ans,sta[top].calc());
  74. suf[i].rt = suf[i + 1].rt,suf[i].top = top;
  75. Tree.modify (suf[i].rt,1,n,top,i,i + sta[top].len - 1);
  76. }
  77. }
  78. signed main(){
  79. read (n,m),inv[1] = 1;
  80. for (Int i = 2;i <= n;++ i) inv[i] = mul (mod - (mod / i),inv[mod % i]);
  81. for (Int i = 1;i <= n;++ i) read (a[i]),sum[i] = sum[i - 1] + Data (1,a[i],mul (a[i],a[i]));Init ();
  82. write (pre[n].ans),putchar ('\n');
  83. for (Int i = 1,x,y;i <= m;++ i){
  84. read (x,y);
  85. int l = 0,r = suf[x + 1].top - 1;
  86. while (l <= r){
  87. int mid = (l + r) >> 1,Rpos = mid ? Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid + 1) : x;//Rpos就是选出来的R
  88. Data tmp = Data (1,y,mul (y,y)) + calc (x + 1,Rpos);int Lpos = x > 1 ? Tree.queryl (pre[x - 1].rt,1,n,pre[x - 1].top,tmp) : x;
  89. if (tmp < calc (Rpos + 1,Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid))) r = mid - 1;
  90. else l = mid + 1;
  91. }
  92. int mid = r + 1,Rpos = mid ? Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid + 1) : x;
  93. Data tmp = Data (1,y,mul (y,y)) + calc (x + 1,Rpos);int Lpos = x > 1 ? Tree.queryl (pre[x - 1].rt,1,n,pre[x - 1].top,tmp) : x;
  94. write (add (tmp.calc(),add (pre[Lpos].ans,suf[Rpos + 1].ans))),putchar ('\n');
  95. }
  96. return 0;
  97. }

题解 [HNOI2019]序列的更多相关文章

  1. 题解-[HNOI2016]序列

    题解-[HNOI2016]序列 [HNOI2016]序列 给定 \(n\) 和 \(m\) 以及序列 \(a\{n\}\).有 \(m\) 次询问,每次给定区间 \([l,r]\in[1,n]\),求 ...

  2. 【题解】Luogu P5294 [HNOI2019]序列

    原题传送门 题意:给你一个长度为\(n\)的序列\(A\),每次询问修改一个元素(只对当前询问有效),然后让你找到一个不下降序列\(B\),使得这两个序列相应位置之差的平方和最小,并输出这个最小平方和 ...

  3. [题解] [SDOI2017] 序列计数

    题面 题解 和 SDOI2015 序列统计 比较像 这个无非就是把乘改成了加, NTT 改成了 MTT 再加上了一个小小的容斥 : 拿所有方案减去不合法方案即可 Code #include <a ...

  4. [题解] [SDOI2015] 序列统计

    题面 题解 设 \(f[i][j]\) 代表长度为 \(i\) 的序列, 乘积模 \(m\) 为 \(j\) 的序列有多少个 转移方程如下 \[ f[i + j][C] = \sum_{A*B\equ ...

  5. luogu P5294 [HNOI2019]序列

    传送门 这个什么鬼证明直接看uoj的题解吧根本不会证明 首先方案一定是若干段等值的\(B\),然后对于一段,\(B\)的值应该是\(A\)的平均值.这个最优方案是可以线性构造的,也就是维护以区间平均值 ...

  6. 题解-bzoj1283序列 & bzoj4842 [Neerc2016]Delight for a Cat

    因为这两题有递进关系,所以放一起写 Problem bzoj1283 序列 题意概要:一个长度为 \(n\) 的序列\(\{c_i\}\),求一个子集,使得原序列中任意长度为 \(m\) 的子串中被选 ...

  7. 【洛谷5294】[HNOI2019] 序列(主席树维护单调栈+二分)

    点此看题面 大致题意: 给你一个长度为\(n\)的序列\(A\),每次询问修改一个元素(只对当前询问有效),然后让你找到一个不下降序列\(B\),使得这两个序列相应位置之差的平方和最小,并输出这个最小 ...

  8. 【Luogu5294】[HNOI2019]序列

    题目链接 题意 给定一个序列,要求将它改造成一个非降序列,修改一个数的代价为其改变量的平方. 最小化总代价. 另有\(Q\) 次询问,每次修改一个位置上的数.(询问之间独立,互不影响) Sol 神仙 ...

  9. bzoj1211树的计数 x bzoj1005明明的烦恼 题解(Prufer序列)

    1211: [HNOI2004]树的计数 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3432  Solved: 1295[Submit][Stat ...

随机推荐

  1. HCNP Routing&Switching之OSPF LSA类型

    前文我们了解了OSPF中的虚连接相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15202348.html:今天我们来聊一聊OSPF数据包中LSA类型相 ...

  2. 安装完anaconda之后找不到启动图标

    安装anaconda的过程中,选择了only me模式,安装完之后找不到启动图标,安装网上的方法: 在命令行输入 conda update menuinstconda install -f conso ...

  3. java 查询当天0点0分0秒

    由于业务需求,要计算客户今日收益,本周本月,本年等收益, 1.查询当天0点0分0秒 2.查询本月一号0点0分0秒 ...... Calendar calendar = Calendar.getInst ...

  4. Mybatis-技术专区-如何清晰的解决出现「多对一模型」和「一对多模型」的问题

    前提介绍 在mybatis如何进行多对一.一对多(一对一)的多表查询呢?本章带你认识如何非常顺滑的解决! 基础使用篇 一对一 association association通常用来映射一对一的关系,例 ...

  5. maven下载出错

    求解

  6. Java如何搭建脚手架(自动生成通用代码),创建自定义的archetype(项目模板)

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  7. 假期作业03:使用IDE开发你的Java程序

    假期作业03:使用IDE开发你的Java程序 一.使用Eclipse创建一个Java项目HelloWorldPrj,编写一个Java程序并运行. 首先要下载eclipse. (注意这里要选一个中国的, ...

  8. 传递集合参数以及SpringMVC和Struts2的区别

    一.传递集合参数 二.和Struts2的区别 Struts2是基于类封装请求参数,SpringMVC是基于方法封装参数:

  9. Baidu初试题分享(Java高级工程师)

    [特别声明:文章仅用来借鉴学习,不用于其他商业化活动] 1.JDK和JRE区别? JDK是整个JAVA的核心,包括了Java运行环境JRE,一堆Java工具和Java基础的类库.通过JDK开发人员将源 ...

  10. Lombok中@Data注解的坑

    开发遇到@Data注解的大坑 如果使用@Data注解,会默认重写hashcode和equals方法 那会遇到什么问题呢? 比如说: @Data public class DataTest { priv ...