【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
#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;
}

你可能感兴趣的:(_____数学_____)