题目链接

题意

给定一个序列,要求将它改造成一个非降序列,修改一个数的代价为其改变量的平方。

最小化总代价。

另有\(Q\) 次询问,每次修改一个位置上的数。(询问之间独立,互不影响)

Sol

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

首先是一个暴力的每次 \(O(n)\) 做法。

结论是: 最后的结果序列一定是一段段的相同的数,其值为段中所有元素的平均数。

所以暴力就是维护一个单调栈。

每次加入一个数后形成一段。

然后不断比较栈顶的段和下面一个段的平均数的大小,如果栈顶小一些就把它和下面那个段合并。

然后考虑多组询问。

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

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

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

发现如果我们确定了左端点那么右端点是唯一确定的。

首先我们要知道从后往前做上面的贪心也是正确的

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

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

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

  1. \(x>p\)

    这时我们显然要把前面那个段给合并进来,那么当前的平均数就会变大。

    由于 \(x\) 往前是单调不升的,所以肯定会在某一个地方停止合并。
  2. \(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 T>inline void Dec(T&x,int y){x-=y;if(x < 0 ) x+=mod;}
template<class T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod) if(k&1) ret=(ll)ret*x%mod;return ret;}
inline int Sum(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int Dif(int x,int y){x-=y;if(x < 0 ) x+=mod;return x;}
int maxnum;int n,m;
int A[N];int ans=0,inv[N];
struct node{
int len;ll sum;int sump;
node(int _l=0,ll _s=0,int _sump=0){len=_l,sum=_s,sump=_sump;}
inline int Calc(){int ms=sum%mod;return Dif(sump,(ll)ms*ms%mod*inv[len]%mod);}
inline node operator +(node b){return node(len+b.len,sum+b.sum,Sum(sump,b.sump));}
inline node operator -(node b){return node(len-b.len,sum-b.sum,Dif(sump,b.sump));}
inline bool operator <(node b)const{
if(!len) return b.len;if(!b.len) return 0;
return (db)sum/len<(db)b.sum/b.len;
}
}Su[N];
inline node Get(int l,int r){if(!l&&!r) return node();return Su[r]-Su[l-1];}
struct seg{
int ls,rs,L,R,Rls;
inline void Merge(seg LS,seg RS){L=LS.L,R=max(RS.R,LS.R);Rls=LS.Rls;}
}T[MAXN];int cnt=0;
int prelen[N],suflen[N];
int preans[N],sufans[N];
int rtpre[N],rtsuf[N];
node stk[N];int top=0;
inline node Pack(int i){return node(1,A[i],(ll)A[i]*A[i]%mod);}
void Modify(int&u,int l,int r,int p,int ML,int MR){
T[++cnt]=T[u];u=cnt;
if(l==r) {T[u].ls=T[u].rs=0,T[u].L=ML,T[u].R=MR;T[u].Rls=MR;return;}
int mid=(l+r)>>1;
if(mid>=p) Modify(T[u].ls,l,mid,p,ML,MR);
else Modify(T[u].rs,mid+1,r,p,ML,MR);
T[u].Merge(T[T[u].ls],T[T[u].rs]);
return;
}
inline int Query_Suf(int u,int l,int r,int p){
if(l==r) return T[u].R;int mid=(l+r)>>1;
if(mid>=p) return Query_Suf(T[u].ls,l,mid,p);return Query_Suf(T[u].rs,mid+1,r,p);
}
inline int Query_Pre(int u,int l,int r,int p,node&Re){
if(r<=p) {
node tw=Get(T[u].Rls+1,T[u].R),tl=Get(T[u].L,T[u].Rls);
if(!(tl<(Re+tw))) {Re=Re+Get(T[u].L,T[u].R);return 0;}
if(l==r) return T[u].R;
}int mid=(l+r)>>1,pos=0;
if(p>mid) pos=Query_Pre(T[u].rs,mid+1,r,p,Re);
if(pos) return pos;return Query_Pre(T[u].ls,l,mid,p,Re);
}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
init(n),init(m);for(int i=1;i<=n;++i) {init(A[i]);Su[i]=Su[i-1]+Pack(i);}
inv[1]=1;for(int i=2;i<=n;++i) inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;top=0;
for(int i=1;i<=n;++i) {
rtpre[i]=rtpre[i-1];stk[++top]=Pack(i);
while(top>1&&stk[top]<stk[top-1]) stk[top-1]=stk[top]+stk[top-1],Modify(rtpre[i],1,n,top,0,0),--top;
Modify(rtpre[i],1,n,top,i-stk[top].len+1,i);
preans[i]=Sum(preans[i-stk[top].len],stk[top].Calc());
prelen[i]=top;
}top=0;
for(int i=n;i;--i) {
rtsuf[i]=rtsuf[i+1];stk[++top]=Pack(i);
while(top>1&&stk[top-1]<stk[top]) stk[top-1]=stk[top]+stk[top-1],Modify(rtsuf[i],1,n,top,0,0),--top;
Modify(rtsuf[i],1,n,top,i,i+stk[top].len-1);
sufans[i]=Sum(sufans[i+stk[top].len],stk[top].Calc());
suflen[i]=top;
}printf("%d\n",preans[n]);
while(m--){
int x,y;init(x),init(y);int dat=A[x];A[x]=y;
int l=0,r=suflen[x+1]-1,pos=r+1;
while(l<=r){
int mid=(l+r)>>1,rp=mid? Query_Suf(rtsuf[x+1],1,n,suflen[x+1]-mid+1):x;
node val=Pack(x)+Get(x+1,rp);
int lp=x>1? Query_Pre(rtpre[x-1],1,n,prelen[x-1],val):1;
if(val<Get(rp+1,Query_Suf(rtsuf[x+1],1,n,suflen[x+1]-mid))) pos=mid,r=mid-1;
else l=mid+1;
}
int rp=pos? Query_Suf(rtsuf[x+1],1,n,suflen[x+1]-pos+1):x;
node val=Pack(x)+Get(x+1,rp);
int lp=x>1? Query_Pre(rtpre[x-1],1,n,prelen[x-1],val):1;
printf("%d\n",Sum(val.Calc(),Sum(preans[lp],sufans[rp+1])));
A[x]=dat;
}return 0;
}

【Luogu5294】[HNOI2019]序列的更多相关文章

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

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

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

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

  3. [HNOI2019]序列(单调栈+二分)

    通过打表证明发现答案就是把序列划分成若干段,每段的b都是这一段a的平均数.50分做法比较显然,就是单调栈维护,每次将新元素当成一个区间插入末尾,若b值不满足单调不降,则将这个区间与单调栈前一个区间合并 ...

  4. 题解 [HNOI2019]序列

    题目传送门 题目大意 给出一个\(n\)个数的数列\(A_{1,2,...,n}\),求出一个单调不减的数列\(B_{1,2,...,n}\),使得\(\sum_{i=1}^{n}(A_i-B_i)^ ...

  5. luogu P5294 [HNOI2019]序列

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

  6. CSP-S2019「Symphony」

    NOTICE:如觉得本文有什么错误或不妥之处,欢迎评论区以及私信交流,反对乱喷,如有一些让人不爽的评论或人身攻击,带来的后果本人一律不负责 准备工作 Day-inf~Day-3 000 every d ...

  7. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

  8. 【loj3059】【hnoi2019】序列

    题目 给出一个长度为 \(n\) 的序列 \(A\) ; 你需要构造一个新的序列\(B\) ,满足: $B_{i} \le B_{i+1} (1 \le i \lt n ) $ $\sum_{i=1} ...

  9. LOJ 3059 「HNOI2019」序列——贪心与前后缀的思路+线段树上二分

    题目:https://loj.ac/problem/3059 一段 A 选一个 B 的话, B 是这段 A 的平均值.因为 \( \sum (A_i-B)^2 = \sum A_i^2 - 2*B \ ...

随机推荐

  1. 点云ICP注册

    原文链接 背景 两个点云要注册在一块,一般分两个步骤:先做一个大致的对齐,也就是所谓的初始注册,一般可以通过一些可靠的点对来计算得到(如图3所示):然后在初始注册的基础上进行精细注册,提升注册的精度( ...

  2. 【Python开发】【神经网络与深度学习】如何利用Python写简单网络爬虫

    平时没事喜欢看看freebuf的文章,今天在看文章的时候,无线网总是时断时续,于是自己心血来潮就动手写了这个网络爬虫,将页面保存下来方便查看   先分析网站内容,红色部分即是网站文章内容div,可以看 ...

  3. 四、Kubernetes_V1.10集群部署-master-创建kubeconfig

    1.生成配置文件 # 创建 TLS Bootstrapping Token # export BOOTSTRAP_TOKEN=$( /dev/urandom | od -An -t x | tr -d ...

  4. xc语言l博客作业03

    问题 答案 这个作业属于那个课程 c语言程序设计ll 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-4/homework/8719 我在 ...

  5. Vue.js官方文档学习笔记(一)起步篇

    Vue.js起步 Vue.js介绍 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库 ...

  6. C语言--浮点数

    在程序中使用浮点数 -- 浮点数的精确性有限 -- 在从c语言中float类型的精确度只到小数点的7位 -- 浮点数只能在一定范围内去相信它 -- 在有精确度高的要求下不要使用浮点数(在算钱的时候,误 ...

  7. 2018icpc宁夏邀请赛网络赛_G_Trouble of Tyrant

    题意 一列\(n\)个点,给定一个特殊的图,有两种边\(E(1,i)\)和\(E(i-1,i)\),多个询问,每次给一个\(d\),求所有路径长度加上\(d\)后1到\(n\)的最短路. 分析 首先这 ...

  8. qt tableview里面添加控件

    在QStyledItemDelegate的paint方法里面 void MyItemModelDeletage::paint(QPainter *painter, const QStyleOption ...

  9. oracle常用函数(2)

    1) trunc函数,用于截断数字, 截断数字,用法为:trunc(n1,n2),n1表示要被截断的数字,n2表示要截断到那位,但是不会进行四舍五入. n2还可以表示负数,表示截断到小数点前,意思就是 ...

  10. JVM常用虚拟机命令汇总

    title: JVM常用虚拟机命令汇总 comments: false date: 2019-07-22 11:45:33 description: 总结一下常用的JVM虚拟机启动命令. catego ...