【Luogu5294】[HNOI2019]序列

该博客介绍了如何解决Luogu5294题目,即给定序列改造成非降序序列的问题。博主讨论了暴力求解方法,即每次O(n)操作,并提出了序列最终会形成相同数值段且值等于段内平均数的结论。进一步,博主探讨了在多组询问下优化算法,通过维护前缀和后缀的单调栈以及使用可持久化线段树,实现O(1)计算最终答案。通过确定序列段的左右端点,利用二分查找策略提高了效率。
摘要由CSDN通过智能技术生成

题目链接

题意

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

Sol

神仙 保序回归 问题,完全不会。

首先是一个暴力的每次 O ( n ) O(n) O(n) 做法。
结论是: 最后的结果序列一定是一段段的相同的数,其值为段中所有元素的平均数。

所以暴力就是维护一个单调栈。
每次加入一个数后形成一段。
然后不断比较栈顶的段和下面一个段的平均数的大小,如果栈顶小一些就把它和下面那个段合并。

然后考虑多组询问。

显然每次重新计算所有的数太呆了,有很多重复计算且没有必要的地方。

一个很直观的想法就是考虑求出最后修改的数所在段的左右端点,这样我们维护一个前缀后缀的答案后就能够 O ( 1 ) O(1) O(1) 算出最后的答案了。

所以我们先对前后缀分别维护好答案以及单调栈(用可持久化线段树)。

发现如果我们确定了左端点那么右端点是唯一确定的。
首先我们要知道从后往前做上面的贪心也是正确的

然后你现在从后往前已经求出了了 [ R + 1 , n ] [R+1,n] [R+1,n] 这些数的单调栈。

当前段的右端点是 R R R,然后计算其左端点。

考虑左边来了一个段,其平均数为 x x x ,当前平均数为 p p p

  1. x > p x>p x>p
    这时我们显然要把前面那个段给合并进来,那么当前的平均数就会变大。
    由于 x x x 往前是单调不升的,所以肯定会在某一个地方停止合并。
  2. x &lt; p x&lt;p x<p
    那么这个时候已经停止合并,只可能在后面某处停止合并。

所以左端点具有可二分性,可以线段树上二分求出。

然后考虑求右端点。
这个同样具有可二分性,因为当前段合法的话往后再并入一个后的平均值小于后面那一个段,就更加小于并入后的后继段了。

二分套二分求出左右端点就能算答案了。

code:

/*
  1. 一个点最后会在的右端点一定是某个后缀单调栈节点的边界?  通过从后往前暴力的正确性可知
  2. 二分的依据&正确性?  非情况讨论出往前加入一系列单调栈节点后平均值的变化,为一个单峰函数
  
 */
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void init(T&x){
   
    x=0;char ch=getchar();bool t=0;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    if(t) x=-x;return;
}typedef long long ll;
typedef double db;
const int N=1e5+10;
const int MAXN=N*80;
const int mod=998244353;
template<class T>inline void Inc(T&x,int y){
   x+=y;if(x>=mod) x-=mod;}
template<class 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值