【HNOI2019】序列(保序回归问题L2)(单调栈)(二分)

传送门


终于写完HNOI2019了。

HNOI2019的题其实都挺好的。有思维难度,有代码难度,有的题还有适当部分的常数优化,考察了各个方面的很多技巧。

题解:

首先这道题发现是个保序回归L2(不知道的可以看18年集训队论文)。

那么我们知道这道题的不带修做法了:

利用单调栈,将 A i A_i Ai分成尽可能少的段,使得每段的平均值单调上升。 B i B_i Bi取对应段 A A A的平均值即可得到最优解。

维护显然只需要段大小,段内 A i A_i Ai之和,段内 A i 2 A_i^2 Ai2之和就行了。

怎么带上修改?

我们发现从前向后做上升单调栈和从后向前做下降单调栈的结果是一样的。

假设我们当前需要处理某个位置 i i i的值的改变,我们得到了 1 1 1 i − 1 i-1 i1的单调栈和 i + 1 i+1 i+1 n n n的单调栈。

我们需要找出 i i i会和两个栈的哪些部分合并。

显然二分套二分。

现在考虑怎么搞出单调栈。

显然可以用可持久化平衡树/线段树维护。但是太慢了。

跑一次 1 − n 1-n 1n的单调栈。记录一下每个位置的弾栈时间,然后询问离线,倒着处理,维护倒着的单调栈,回退一下正着的单调栈就行了。

由于有一个二分套二分,复杂度为 O ( n + m log ⁡ 2 n ) O(n+m\log^2n) O(n+mlog2n)


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T=int>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get();}
}
using namespace IO;

using std::cerr;
using std::cout;
using pii=std::pair<int,int>;
#define fi first
#define se second

cs int N=1e5+7,mod=998244353;
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}

int n,m;

int st1[N],st2[N];
int a[N],inv[N];
int S1[N],S2[N],t1,t2;
ll S[N];

int ans[N];

std::vector<pii> q[N];
std::vector<int> b[N];

inline int get_ave(int l,int r,int ad=0){
	ll val=S[r]-S[l-1]+ad;val%=mod;
	return mod-val*val%mod*inv[r-l+1]%mod;
}

inline bool check(int l1,int r1,int l2,int r2,int ad1=0,int ad2=0){
	return (S[r1]-S[l1-1]+ad1)*(r2-l2+1)>(S[r2]-S[l2-1]+ad2)*(r1-l1+1);
}

signed main(){
#ifdef zxyoi
	freopen("sequence.in","r",stdin);
#endif
	n=gi(),m=gi();int sum=0;
	for(int re i=1;i<=n;++i){
		a[i]=gi();S[i]=S[i-1]+a[i];
		sum=(sum+(ll)a[i]*a[i])%mod;
	}inv[1]=1;
	for(int re i=2;i<=n;++i)inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
	ans[0]=sum;q[1].push_back(pii(a[1],0));
	for(int re i=1;i<=m;++i){
		int x=gi(),y=gi();
		q[x].push_back(pii(y,i));
		ans[i]=(sum+(ll)(mod-a[x])*a[x]+(ll)y*y)%mod;
	}
	for(int re i=1;i<=n;++i){
		while(t1&&check(st1[t1-1]+1,st1[t1],st1[t1]+1,i))
		b[i].push_back(st1[t1--]);
		st1[++t1]=i;
		S1[t1]=(S1[t1-1]+get_ave(st1[t1-1]+1,i))%mod;
	}
	st2[0]=n+1;
	for(int re i=n;i;--i){
		--t1;
		std::reverse(b[i].begin(),b[i].end());
		for(int t:b[i]){
			st1[++t1]=t;
			S1[t1]=(S1[t1-1]+get_ave(st1[t1-1]+1,t))%mod;
		}
		if(i<n){
			while(t2&&check(i+1,st2[t2]-1,st2[t2],st2[t2-1]-1))--t2;
			st2[++t2]=i+1;
			S2[t2]=(S2[t2-1]+get_ave(i+1,st2[t2-1]-1))%mod;
		}
		for(pii t:q[i]){
			int l=1,r=t1,tp=0,d=t.fi-a[i];
			while(l<=r){
				int mid=l+r>>1;
				if(check(st1[mid-1]+1,st1[mid],st1[mid]+1,i,0,d))r=mid-1;
				else tp=mid,l=mid+1;
			}
			if(!t2||!check(st1[tp]+1,i,i+1,st2[t2-1]-1,d)){
				Inc(ans[t.se],S1[tp]);
				Inc(ans[t.se],S2[t2]);
				Inc(ans[t.se],get_ave(st1[tp]+1,i,d));
			}
			else {
				l=0,r=t2-1;
				int rt=0,lt=0;
				while(l<=r){
					int m=l+r>>1,L=1,R=tp,tp2=0;
					while(L<=R){
						int M=L+R>>1;
						if(check(st1[M-1]+1,st1[M],st1[M]+1,st2[m]-1,0,d))R=M-1;
						else tp2=M,L=M+1;
					}
					if(m&&check(st1[tp2]+1,st2[m]-1,st2[m],st2[m-1]-1,d))r=m-1;
					else rt=m,lt=tp2,l=m+1;
				}
				Inc(ans[t.se],S1[lt]);
				Inc(ans[t.se],S2[rt]);
				Inc(ans[t.se],get_ave(st1[lt]+1,st2[rt]-1,d));
			}
		}
	}
	for(int re i=0;i<=m;++i)cout<<ans[i]<<"\n";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值