[HNOI2016]序列
对于莫队算法最主要的是如何快速算出
[
l
,
r
]
→
[
l
,
r
+
1
]
[l,r]\to[l,r+1]
[l,r]→[l,r+1]对答案的贡献的变化。
当询问区间发生上述变化时
[
l
,
r
]
→
[
l
,
r
+
1
]
[l,r]\to [l,r+1]
[l,r]→[l,r+1]不难发现子区间增加这些:
[
l
,
r
+
1
]
,
[
l
+
1
,
r
+
1
]
,
…
,
[
r
,
r
+
1
]
,
[
r
+
1
,
r
+
1
]
[l,r+1],[l+1,r+1],\dots,[r,r+1],[r+1,r+1]
[l,r+1],[l+1,r+1],…,[r,r+1],[r+1,r+1]总共
r
−
l
+
2
r-l+2
r−l+2个子区间
[
l
→
r
+
1
,
r
+
1
]
[l\to r+1,r+1]
[l→r+1,r+1],需要求出他们对答案的贡献即每个区间的区间最小值之和。
考虑求出 [ l , r + 1 ] [l,r+1] [l,r+1]区间最小值的位置是 p p p,不难得知这些子区间 [ l → p , r + 1 ] [l\to p,r+1] [l→p,r+1]的最小值都是 a p a_p ap,这部分对答案的贡献是 a p × ( p − l + 1 ) a_p×(p-l+1) ap×(p−l+1)这里可以用ST表预处理快速求出。
而对于这些子区间
[
p
+
1
→
r
+
1
,
r
+
1
]
[p+1\to r+1,r+1]
[p+1→r+1,r+1]
预处理
f
i
f_i
fi:表示以
i
i
i为区间右端点的最小值之和
f
i
=
f
L
i
+
a
i
×
(
i
−
L
i
)
f_i=f_{L_i}+a_i×(i-L_i)
fi=fLi+ai×(i−Li)
L
i
L_i
Li表示
i
i
i左边第一个小于等于
a
i
a_i
ai的位置。(可以用单调栈预处理)
不难发现一定有
x
x
x使得
L
x
=
p
L_x=p
Lx=p于是有
f
r
+
1
=
a
r
+
1
×
(
r
+
1
−
L
r
+
1
)
+
⋯
+
a
x
×
(
x
−
p
)
+
f
p
f_{r+1}=a_{r+1}×(r+1-L_{r+1})+\dots+a_x×(x-p)+f_p
fr+1=ar+1×(r+1−Lr+1)+⋯+ax×(x−p)+fp
于是子区间
[
p
+
1
→
r
+
1
,
r
+
1
]
[p+1\to r+1,r+1]
[p+1→r+1,r+1]对答案的贡献是
f
r
+
1
−
f
p
f_{r+1}-f_p
fr+1−fp
对于 [ l , r ] → [ l − 1 , r ] [l,r]\to[l-1,r] [l,r]→[l−1,r]可以效仿移动右端点的方式预处理 R i R_i Ri以及 g i g_i gi即可。
时间复杂度 O ( n log n + n n ) O(n\log n+n\sqrt{n}) O(nlogn+nn)
注意莫队首先移动右端点因为初始化时, l = 1 , r = 0 l=1,r=0 l=1,r=0
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using pii=pair<int,int>;
using ll=long long;
constexpr int N=100010;
pii ST[N][22];
int lg[N];
int a[N],n,m;
int L[N],R[N],stk[N],top;
ll f[N],g[N];
int sz,pos[N];
void init(int n)
{
lg[1]=0;
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++) ST[i][0]={a[i],i};
for(int k=1;k<=lg[n];k++)
for(int i=1;i+(1<<k)-1<=n;i++)
ST[i][k]=min(ST[i][k-1],ST[i+(1<<k-1)][k-1]);
top=0;
for(int i=1;i<=n;i++)
{
while(top&&a[stk[top]]>a[i]) top--;
L[i]=stk[top];
stk[++top]=i;
}
stk[top=0]=n+1;
for(int i=n;i>=1;i--)
{
while(top&&a[stk[top]]>a[i]) top--;
R[i]=stk[top];
stk[++top]=i;
}
for(int i=1;i<=n;i++)
f[i]=f[L[i]]+1ll*(i-L[i])*a[i];
for(int i=n;i>=1;i--)
g[i]=g[R[i]]+1ll*(R[i]-i)*a[i];
sz=sqrt(n);
for(int i=1;i<=n;i++) pos[i]=i/sz;
}
int query(int l,int r)// [l,r]最小值的位置
{
int k=lg[r-l+1];
return min(ST[l][k],ST[r-(1<<k)+1][k]).second;
}
struct node
{
int l,r;
int id;
bool operator<(const node&o)const
{
return pos[l]<pos[o.l]||pos[l]==pos[o.l]&&r<o.r;
}
}q[N];
ll calcR(int l,int r)
{
int p=query(l,r);
return 1ll*a[p]*(p-l+1)+f[r]-f[p];
}
ll calcL(int l,int r)
{
int p=query(l,r);
return 1ll*a[p]*(r-p+1)+g[l]-g[p];
}
ll ans[N],res;
int main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
init(n);
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
q[i]={l,r,i};
}
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
while(r<q[i].r) res+=calcR(l,++r);//首先考虑右端点
while(r>q[i].r) res-=calcR(l,r--);
while(l<q[i].l) res-=calcL(l++,r);
while(l>q[i].l) res+=calcL(--l,r);
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
return 0;
}
注意我改了2小时,对比代码才改出来,无语了~~~
要加油哦~