【BZOJ4540】序列(HNOI2016)-莫队算法+RMQ
测试地址:序列
做法:本题需要用到莫队算法+RMQ。
首先看到询问不强制在线,并且没有修改,显然非常莫队,那么就来到了这道题的难点:如何处理区间扩张或收缩时答案的变化。
我们发现往区间中增加或减少一个元素,实际上就是添加或减少以这个元素为开头或结尾的一些子段,显然左右是对称的,所以下面只考虑在区间右端增减元素的情况。我们令为添加子段能得到的贡献,再令为第个元素左边第一个比它小的元素的位置,则有:
注意到的贡献就是由一段一段像这样的贡献拼起来的,我们可以把这个看成从向连了一条边权为的边,显然这样连出来的是一棵树,那么我们要求的贡献实际上就相当于,从第个元素一直往上跳,一边跳一边累计边权,一直到它指向的元素在序列中的位置小于为止,这个时候还剩下一些贡献,显然最后停下来的节点的值为区间中的最小值,那么最后区间还有多少个元素,就有多少个这样的贡献,直接RMQ并预处理树上的前缀和即可做到查询,上面提到的用单调栈预处理一下即可。
于是我们就解决了这一题,时间复杂度为。
(话说我今天才知道RMQ可以做到查询……我可能是太菜了吧)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,q,st[100010],top,l[100010],r[100010];
int blocksiz,nowl,nowr,len[100010]={0},mnp[100010][21];
ll a[100010],mn[100010][21]={0},lft[100010],rht[100010];
ll ans,Ans[100010];
struct query
{
int l,r,id;
}Q[100010];
bool cmp(query a,query b)
{
if (a.l/blocksiz!=b.l/blocksiz)
return a.l/blocksiz<b.l/blocksiz;
else return a.r<b.r;
}
void init()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
top=0;
for(int i=1;i<=n;i++)
{
l[i]=i;
while(top>0&&a[st[top]]>a[i])
{
r[st[top]]=i-1;
l[i]=l[st[top]];
top--;
}
st[++top]=i;
}
while(top)
{
r[st[top]]=n;
top--;
}
}
void init_rmq()
{
for(int i=1;i<=n;i++)
mn[i][0]=a[i],mnp[i][0]=i;
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
{
if (j+(1<<(i-1))-1>=n)
{
mn[j][i]=mn[j][i-1];
mnp[j][i]=mnp[j][i-1];
continue;
}
mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);
if (mn[j][i-1]<mn[j+(1<<(i-1))][i-1])
mnp[j][i]=mnp[j][i-1];
else mnp[j][i]=mnp[j+(1<<(i-1))][i-1];
}
int x=0;
for(int i=1;i<=n;i++)
{
if (i>(1<<(x+1))) x++;
len[i]=x;
}
}
void init_sum()
{
lft[0]=0;
for(int i=1;i<=n;i++)
lft[i]=lft[l[i]-1]+(ll)(i-l[i]+1)*a[i];
rht[n+1]=0;
for(int i=n;i>=1;i--)
rht[i]=rht[r[i]+1]+(ll)(r[i]-i+1)*a[i];
}
int rmq(int l,int r)
{
int L=len[r-l+1];
if (mn[l][L]<mn[r-(1<<L)+1][L]) return mnp[l][L];
else return mnp[r-(1<<L)+1][L];
}
ll query(int l,int r,bool side)
{
int mp=rmq(l,r);
ll sum=0;
if (side)
{
sum+=lft[r]-lft[mp];
sum+=(ll)(mp-l+1)*a[mp];
}
else
{
sum+=rht[l]-rht[mp];
sum+=(ll)(r-mp+1)*a[mp];
}
return sum;
}
void extend(int x,bool type,bool side)
{
if (type) ans+=query(nowl,nowr,side);
else ans-=query(nowl,nowr,side);
}
void Mo()
{
for(int i=1;i<=q;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
blocksiz=sqrt(n);
sort(Q+1,Q+q+1,cmp);
nowl=1,nowr=0;
ans=0;
for(int i=1;i<=q;i++)
{
while(Q[i].l<nowl) extend(--nowl,1,0);
while(Q[i].r>nowr) extend(++nowr,1,1);
while(Q[i].l>nowl) extend(nowl,0,0),nowl++;
while(Q[i].r<nowr) extend(nowr,0,1),nowr--;
Ans[Q[i].id]=ans;
}
for(int i=1;i<=q;i++)
printf("%lld\n",Ans[i]);
}
int main()
{
init();
init_rmq();
init_sum();
Mo();
return 0;
}