[UOJ213][UNR #1]争夺圣杯

时间:2022-01-07 17:16:14

uoj

description

一个长为\(n\)的序列,给定一个参数\(m\),求所有长度为\(m\)的区间的最大值之和。

对于所有的\(m\in[1,n]\)你都需要分别求出答案然后异或起来。

\(n\le10^6\)

sol

枚举区间长度\(m\)看上去不好做,我们改变一下顺序,枚举每个位置\(i\),考虑它对每个长度的答案的贡献。

设\(L_i\)为\(i\)左边第一个大于等于\(a_i\)的数的出现位置,\(R_i\)为\(i\)右边第一个大于(一定需要有一边不能取等)\(a_i\)的数的出现位置。

那么显然\(a_i\)这个数能够贡献的区间就必须满足\(l\in[L_i+1,i],r\in[i,R_i-1]\)。

设\(p=\min(i-L_i,R_i-i),q=\max(i-L_i,R_i-i)\)

对于长度\(x\in[1,p-1]\)的区间,\(a_i\)对它的贡献是\(x\times a_i\)。

对于长度\(x\in[p,q-1]\)的区间,\(a_i\)对它的贡献是\(p\times a_i\)。

对于长度\(x\in[q,p+q-1]\)的区间,\(a_i\)对它的贡献是\((p+q-x)\times a_i\)。

三段分别处理,相等于是在答案数组上区间加一个一次函数,开两个差分数组分别维护一下就行了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e6+5;
const int mod = 998244353;
int n,a[N],L[N],R[N],S[N],top,s[N],ss[N],ans;
inline void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
inline void mns(int &x,int y){x-=y;if(x<0)x+=mod;}
void cover(int l,int r,int a,int b){
add(ss[l],a);mns(ss[r+1],a);
add(s[l],b);mns(s[r+1],b);
}
int main(){
n=gi();
for (int i=1;i<=n;++i) a[i]=gi();
for (int i=1;i<=n;++i){
while (top&&a[S[top]]<=a[i]) --top;
L[i]=S[top];S[++top]=i;
}
S[top=0]=n+1;
for (int i=n;i;--i){
while (top&&a[S[top]]<a[i]) --top;
R[i]=S[top];S[++top]=i;
}
for (int i=1;i<=n;++i){
int p=i-L[i],q=R[i]-i;if (p>q) swap(p,q);a[i]%=mod;
cover(1,p-1,a[i],0);
cover(p,q-1,0,1ll*p*a[i]%mod);
cover(q,p+q-1,mod-a[i],1ll*(p+q)*a[i]%mod);
}
for (int i=1;i<=n;++i) add(s[i],s[i-1]),add(ss[i],ss[i-1]),ans^=(1ll*ss[i]*i+s[i])%mod;
printf("%d\n",ans);return 0;
}