P2048 【NOI2010】 超级钢琴

2023NOIP A层联测9 风信子

一年 OI 一场空,一道原题见祖宗……

Ps:超级钢琴是风信子的前置题。

超级钢琴

题意

在一段序列上,选择长度为 \(x\) 的区间且 \(x\in [L,R]\),求选择 \(k\) 个区间求和的最大值。

思路

来自洛谷第一篇 Nekroz 的题解。

将区间和变为前缀和,考虑将所有的合法方案的和拉出来排序,时间复杂度不现实,考虑贪心的解决这个问题。

设 \(S(o,l,r)=max(sum[t]-sum[o-1])|l\leq t \leq r\),即以 \(o\) 为左端点,以 \(t\) 为右端点的区间的和。\(sum\) 是前缀和,\(l,r\) 使得 \(t\) 满足题目限制。

维护 \(sum[t]\) 可以使用 \(ST\) 表 \(O(1)\) 维护。

然后考虑贪心。

我们将每次可以选择最优的 \(S(o,l,r)\),选择 \(k\) 次就是我们的结果。

初始时,是 \(n\) 个 \(i\ (i\in [1,n])\) 为起点,终点范围最大的区间,这里可以用优先队列维护(用结构体存 \(S(o,l,r)\),写构造函数比较大小)。

三元组 \(S(o,l,r)\) 选择后,会增加 \(S(o,l,t-1)\) 和 \(S(o,t+1,r)\) 两个答案区间,同时 \(S(o,l,r)\) 这个区间不能再被选中,先弹出不能选的区间,再把这两个玩意丢进堆里。对于 \(l=t\) 或 \(r=t\) 的情况需要特判。

那么这个分裂的正确性在哪里呢?

约定:下文称 \(S(o,l,r)\) 选中后分裂出的三元组为子三元组,\(S(o,l,r)\) 为父三元组。

Q:子三元组有没有可能大于父三元组,导致答案变劣。

A:不难发现,我们分裂出这个三元组的父三元组肯定大于这个三元组(因为起点相同,结束端点父三元组肯定选最大的)。所以只有父三元组被选,子三元组才会被选。

Q:父三元组选择的结束端点影响子三元组的取值,是否存在结束端点使父三元组变略,而使子三元组和父三元组共同的贡献更优。

A:每次三元组本质是一段区间,如果父三元组不选择最大段区间,肯定存在子三元组会选择最大区间,其实分到最后,每一个区间都会出现一次。其实上文的父子三元组单调性也证明了不会出现这种情况。

CODE

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. const int maxn=5e5+5;
  5. int n,k,l,r;
  6. int sum[maxn];
  7. struct Tree
  8. {
  9. int l,r;
  10. pair<int,int>mx={-1e9,-1e9};
  11. }tree[maxn*10];
  12. void build(int p,int l,int r)
  13. {
  14. tree[p].l=l;
  15. tree[p].r=r;
  16. if(l==r)
  17. {
  18. tree[p].mx=make_pair(sum[l],l);
  19. return ;
  20. }
  21. build(p*2,l,(l+r)>>1);
  22. build(p*2+1,((l+r)>>1)+1,r);
  23. tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);
  24. }
  25. pair<int,int> query(int p,int l,int r)
  26. {
  27. if(tree[p].l>r||tree[p].r<l) return make_pair(-1e9,-1e9);
  28. if(l<=tree[p].l&&tree[p].r<=r) return tree[p].mx;
  29. return max(query(p*2,l,r),query(p*2+1,l,r));
  30. }
  31. struct element
  32. {
  33. int o,l,r,t;
  34. friend bool operator<(element a,element b){return (sum[a.t]-sum[a.o-1])<(sum[b.t]-sum[b.o-1]);}
  35. };
  36. element gt(int o,int l,int r)
  37. {
  38. return {o,l,r,query(1,l,r).second};
  39. }
  40. priority_queue<element>que;
  41. signed main()
  42. {
  43. scanf("%lld%lld%lld%lld",&n,&k,&l,&r);
  44. for(int i=1;i<=n;i++)
  45. {
  46. int x;
  47. scanf("%lld",&x);
  48. sum[i]=sum[i-1]+x;
  49. }
  50. build(1,1,n);
  51. for(int i=1;i+l-1<=n;i++) que.push(gt(i,i+l-1,min(i+r-1,n)));
  52. int ans=0;
  53. while(k--)
  54. {
  55. int o=que.top().o,l=que.top().l,r=que.top().r,t=que.top().t;
  56. que.pop();
  57. ans+=sum[t]-sum[o-1];
  58. if(t!=l) que.push(gt(o,l,t-1));
  59. if(t!=r) que.push(gt(o,t+1,r));
  60. }
  61. printf("%lld",ans);
  62. }

风信子

题面

有两种操作

1.选择区间 \([l,r]\) 使 \(i\in [l,r]\) 中的 \(a_i\) 都加上 \(x\)。

2.在区间 \([l,r]\) 选择 \(k\) 个数对 \((i,j)\ (i\leq j)\),求 \(a_i-a_j\) 的和的最大值。

思路

\(50pts\):查询询问做一次超级钢琴,线段树区间加。

\(15pts(k=1)\):线段树维护区间答案,对于一个节点,答案可以是左右儿子的答案,也可以是左边最大-右边最小。

\(100pts\):

超级钢琴中,我们的”候补答案集合“思想是以每个点为起点做一次做三元组。

这显然不够带劲,在这里我们把起点也设成区间,那么也就是:\(S(l,r,l',r')\),其中 \([l,r]\) 是起点,\([l',r']\) 是终点。

但这样子选择区间后不方便分裂,那么我们考虑,什么样的区间分裂比较方便?

1.起点终点区间完全重合。(\(k=1\) 的做法)

2.起点终点区间没有交集。(起点取区间最大,终点去区间最小)

利用超级钢琴的思想维护优先队列即可。

这里分裂后我们的区间要满足上述两种条件,所以说最终区间如下。

维护很麻烦,但思路简单。

CODE

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. #define ls(i) i*2
  5. #define rs(i) i*2+1
  6. const int maxn=1e5+5;
  7. const int inf=1e9;
  8. int n,m;
  9. int a[maxn];
  10. struct Ans
  11. {
  12. int val,x,y;
  13. friend bool operator<(Ans a,Ans b){return a.val<b.val;}
  14. friend bool operator>(Ans a,Ans b){return a.val>b.val;}
  15. };
  16. struct Grid
  17. {
  18. int val,id;
  19. friend bool operator<(Grid a,Grid b){return a.val<b.val;}
  20. friend bool operator>(Grid a,Grid b){return a.val>b.val;}
  21. };
  22. struct node1
  23. {
  24. int l,r,mx=-inf,mi=inf,mxid,miid,lazy;
  25. Ans val;
  26. }tree[maxn*10];
  27. inline void updata(int p)
  28. {
  29. tree[p].val=max(tree[ls(p)].val,tree[rs(p)].val);
  30. Ans tmp;
  31. tmp.val=tree[ls(p)].mx-tree[rs(p)].mi;
  32. tmp.x=tree[ls(p)].mxid,tmp.y=tree[rs(p)].miid;
  33. tree[p].val=max(tree[p].val,tmp);
  34. if(tree[ls(p)].mi>tree[rs(p)].mi) tree[p].mi=tree[rs(p)].mi,tree[p].miid=tree[rs(p)].miid;
  35. else tree[p].mi=tree[ls(p)].mi,tree[p].miid=tree[ls(p)].miid;
  36. if(tree[ls(p)].mx<tree[rs(p)].mx) tree[p].mx=tree[rs(p)].mx,tree[p].mxid=tree[rs(p)].mxid;
  37. else tree[p].mx=tree[ls(p)].mx,tree[p].mxid=tree[ls(p)].mxid;
  38. }
  39. inline void push_down(int p)
  40. {
  41. if(tree[p].l==tree[p].r)
  42. {
  43. tree[p].lazy=0;
  44. return ;
  45. }
  46. tree[ls(p)].lazy+=tree[p].lazy;
  47. tree[ls(p)].mx+=tree[p].lazy;
  48. tree[ls(p)].mi+=tree[p].lazy;
  49. tree[rs(p)].lazy+=tree[p].lazy;
  50. tree[rs(p)].mx+=tree[p].lazy;
  51. tree[rs(p)].mi+=tree[p].lazy;
  52. tree[p].lazy=0;
  53. }
  54. inline void build(int p,int l,int r)
  55. {
  56. tree[p].l=l;
  57. tree[p].r=r;
  58. if(l==r)
  59. {
  60. tree[p].mx=tree[p].mi=a[l];
  61. tree[p].mxid=tree[p].miid=l;
  62. tree[p].val.x=tree[p].val.y=l;
  63. return ;
  64. }
  65. build(ls(p),l,l+r>>1);
  66. build(rs(p),(l+r>>1)+1,r);
  67. updata(p);
  68. }
  69. inline Grid gtmi(int p,int l,int r)
  70. {
  71. push_down(p);
  72. if(tree[p].l>r||tree[p].r<l) return {inf,inf};
  73. if(l<=tree[p].l&&tree[p].r<=r) return {tree[p].mi,tree[p].miid};
  74. return min(gtmi(ls(p),l,r),gtmi(rs(p),l,r));
  75. }
  76. inline Grid gtmx(int p,int l,int r)
  77. {
  78. push_down(p);
  79. if(tree[p].l>r||tree[p].r<l) return {-inf,0};
  80. if(l<=tree[p].l&&tree[p].r<=r) return {tree[p].mx,tree[p].mxid};
  81. return max(gtmx(ls(p),l,r),gtmx(rs(p),l,r));
  82. }
  83. inline Ans gtans(int p,int l,int r)
  84. {
  85. push_down(p);
  86. if(tree[p].l>r||tree[p].r<l) return {-inf,0,0};
  87. if(l<=tree[p].l&&tree[p].r<=r) return tree[p].val;
  88. int mid=l+r>>1;
  89. Ans t=max(gtans(ls(p),l,r),gtans(rs(p),l,r));
  90. Grid a=gtmx(ls(p),l,r),b=gtmi(rs(p),l,r);
  91. return max(t,(Ans){a.val-b.val,a.id,b.id});
  92. }
  93. inline void insert(int p,int l,int r,int val)
  94. {
  95. push_down(p);
  96. if(tree[p].r<l||tree[p].l>r) return ;
  97. if(l<=tree[p].l&&tree[p].r<=r)
  98. {
  99. tree[p].lazy+=val;
  100. tree[p].mx+=val;
  101. tree[p].mi+=val;
  102. return ;
  103. }
  104. insert(ls(p),l,r,val);
  105. insert(rs(p),l,r,val);
  106. updata(p);
  107. }
  108. struct preAns
  109. {
  110. int al,ar,bl,br,val;
  111. Ans Val()
  112. {
  113. if(ar<bl)
  114. {
  115. Grid a=gtmx(1,al,ar),b=gtmi(1,bl,br);
  116. return {a.val-b.val,a.id,b.id};
  117. }
  118. else return gtans(1,al,ar);
  119. }
  120. friend bool operator<(preAns a,preAns b){return a.val<b.val;}
  121. friend bool operator>(preAns a,preAns b){return a.val>b.val;}
  122. };
  123. priority_queue<preAns>que;
  124. signed main()
  125. {
  126. freopen("D.in","r",stdin);
  127. freopen("D.out","w",stdout);
  128. scanf("%lld%lld",&n,&m);
  129. for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
  130. build(1,1,n);
  131. while(m--)
  132. {
  133. int op,l,r,k;
  134. scanf("%lld%lld%lld%lld",&op,&l,&r,&k);
  135. if(op==1) insert(1,l,r,k);
  136. else
  137. {
  138. preAns t;
  139. t.al=t.bl=l,t.ar=t.br=r;
  140. t.val=t.Val().val;
  141. que.push(t);
  142. int sum=0;
  143. while(k--)
  144. {
  145. preAns now=que.top();
  146. que.pop();
  147. Ans val=now.Val();
  148. int x=val.x,y=val.y;
  149. sum+=now.val;
  150. if(now.ar<now.bl)
  151. {
  152. if (x > now.al)
  153. {
  154. t=now;
  155. t.ar=x-1;
  156. t.val = t.Val().val;
  157. que.push(t);
  158. }
  159. if (now.bl < y)
  160. {
  161. t=now;
  162. t.ar=t.al=x;
  163. t.br=y-1;
  164. t.val = t.Val().val;
  165. que.push(t);
  166. }
  167. if (y < now.br)
  168. {
  169. t=now;
  170. t.ar=t.al=x;
  171. t.bl=y+1;
  172. t.val = t.Val().val;
  173. que.push(t);
  174. }
  175. if (x < now.ar)
  176. {
  177. t=now;
  178. t.al=x+1;
  179. t.val = t.Val().val;
  180. que.push(t);
  181. }
  182. }
  183. else
  184. {
  185. if(x>now.al)
  186. {
  187. t=now;
  188. t.ar=t.br=x-1;
  189. t.val=t.Val().val;
  190. que.push(t);
  191. t=now;
  192. t.ar=x-1,t.bl=x;
  193. t.val=t.Val().val;
  194. que.push(t);
  195. }
  196. if(x!=y)
  197. {
  198. t.al=t.ar=t.bl=t.br=x;
  199. t.val=t.Val().val;
  200. que.push(t);
  201. }
  202. if(x<y-1)
  203. {
  204. t.al=t.ar=x;
  205. t.bl=x+1;
  206. t.br=y-1;
  207. t.val=t.Val().val;
  208. que.push(t);
  209. }
  210. if(y<now.br)
  211. {
  212. t.al=t.ar=x;
  213. t.br=now.br;
  214. t.bl=y+1;
  215. t.val=t.Val().val;
  216. que.push(t);
  217. }
  218. if(x<now.ar)
  219. {
  220. t=now;
  221. t.al=t.bl=x+1;
  222. t.val=t.Val().val;
  223. que.push(t);
  224. }
  225. }
  226. }
  227. printf("%lld\n",sum);
  228. while(!que.empty()) que.pop();
  229. }
  230. }
  231. }

2023NOIP A层联测9 风信子+P2048 【NOI2010】 超级钢琴 2023的更多相关文章

  1. P2048 [NOI2010]超级钢琴(RMQ+堆+贪心)

    P2048 [NOI2010]超级钢琴 区间和--->前缀和做差 多次查询区间和最大--->前缀和RMQ 每次取出最大的区间和--->堆 于是我们设个3元组$(o,l,r)$,表示左 ...

  2. 洛谷 P2048 [NOI2010]超级钢琴 解题报告

    P2048 [NOI2010]超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为 ...

  3. 【题解】P2048 [NOI2010]超级钢琴

    [题解][P2048 NOI2010]超级钢琴 一道非常套路的题目.是堆的套路题. 考虑前缀和,我们要是确定了左端点,就只需要在右端区间查询最大的那个加进来就好了.\(sum_j-sum_{i-1}​ ...

  4. [洛谷P2048] [NOI2010] 超级钢琴

    洛谷题目链接:[NOI2010]超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号 ...

  5. 洛谷 P2048 [NOI2010]超级钢琴 || Fantasy

    https://www.luogu.org/problemnew/show/P2048 http://www.lydsy.com/JudgeOnline/problem.php?id=2006 首先计 ...

  6. Luogu P2048 [NOI2010]超级钢琴

    这道题题号很清新啊!第一次开NOI的题,因为最近考到了这道题的升级版. 我们先考虑\(O(n^2)\)大暴力,就是枚举前后端点然后开一个前缀和减一下即可. 然后引入正解,我们设一个三元组\(F(s,l ...

  7. P2048 [NOI2010]超级钢琴 (RMQ,堆)

    大意: 给定n元素序列a, 定义一个区间的权值为区间内所有元素和, 求前k大的长度在[L,R]范围内的区间的权值和. 固定右端点, 转为查询左端点最小的前缀和, 可以用RMQ O(1)查询. 要求的是 ...

  8. P2048 [NOI2010]超级钢琴

    传送门 考虑维护前缀和 $sum[i]$ 那么对于每一个位置 $i$ ,左端点为 $i$ 右端点在 $[i+L-1,i+R-1]$ 区间的区间最大值容易维护 维护三元组 $(o,l,r)$ ,表示左端 ...

  9. 洛谷 P2048 [NOI2010]超级钢琴(优先队列,RMQ)

    传送门 我们定义$(p,l,r)=max\{sum[t]-sum[p-1],p+l-1\leq t\leq p+r-1 \}$ 那么因为对每一个$p$来说$sum[p-1]$是一个定值,所以我们只要在 ...

  10. 洛谷P2048 [NOI2010]超级钢琴 题解

    2019/11/14 更新日志: 近期发现这篇题解有点烂,更新一下,删繁就简,详细重点.代码多加了注释.就酱紫啦! 正解步骤 我们需要先算美妙度的前缀和,并初始化RMQ. 循环 \(i\) 从 \(1 ...

随机推荐

  1. 为什么要使用Java SPI机制

    Java SPI(Service Provider Interface)最早是在Java SE 6中被引入的,作为一种标准的.用于在运行时发现和加载服务提供者插件的标准机制.以前的程序猿实现JDBC连 ...

  2. JVM学习笔记之类装载器-ClassLoader

    JVM学习笔记之类装载器-ClassLoader 本文字数:2300,阅读耗时7分钟 JVM体系结构概览 类装载器ClassLoader: 负责加载class文件,class文件在文件开头有特定的文件 ...

  3. c程序设计语言 by K&R(四)输入与输出

    一.标准输入.输出 1. 简单的输入\输出机制 从标准输入中一次读取一个字符:int getchar(void) 将字符c送到标准输出中: int putchar(int) 2. 输入重定向 如果程序 ...

  4. GitHub Copilot 典型使用场景实践

    大家好,我是Edison. 近期我们一直在使用GitHub Copilot协助开发编码工作,总结了一些实际场景的用法,可能在目前网络中很多的博客中都没有提及到,本文一一分享给你. 简介:你的结对编程伙 ...

  5. 自己服务器搭建docker组和环境

    1. docker 当然首先安装一下docker,具体怎么 安装,网上搜一下.我用的ubuntu20系统,就是安装一个普通的软件的操作.安装后,运行一下docker run hello-world,运 ...

  6. AI实战 | 领克汽车线上营销助手:全面功能展示与效果分析

    助手介绍 我就不自我介绍了,在我的智能体探索之旅中,很多人已经通过coze看过我的教程.今天,我专注于分享我所开发的一款助手--<领克汽车线上营销>. 他不仅仅是一个销售顾问的替身,更是一 ...

  7. Angular Material 18+ 高级教程 – CDK Overlay

    Overlay, Dialog, Modal, Popover 傻傻分不清楚 参考: Medium – Modal?Dialog?你真的知道他們是什麼嗎? Popups, dialogs, toolt ...

  8. IOI2000 邮局 加强版 题解

    [IOI2000] 邮局 加强版 题解 考虑动态规划,设 \(f_{i,j}\) 为经过了 \(i\) 个村庄,正在建第 \(j\)​ 个邮局的最优距离. 以及 \(w_{i,j}\) 表示区间 \( ...

  9. 学好QT框架之后可以做什么工作?QT技术框架现代化行业大型复杂应用的经典成功案例

    简介 本文粗略的介绍了QT框架的软件开发技术生态体系的全球影响力:QT框架在文字办公领域.CAD三维图形领域.Linux操作系统领域.物联网领域.汽车电子领域以及数字医疗领域等现代化行业的大型复杂应用 ...

  10. [OI] 偏序

    \(n\) 维偏序即给出若干个点对 \((a_{i},b_{i},\cdots,n_{i})\),对每个 \(i\) 求出满足 \(a_{j}\gt a_{i},b_{j}\gt b_{i}\cdot ...