Problem
Description
影魔,奈文摩尔,据说有着一个诗人的灵魂。事实上,他吞噬的诗人灵魂早已成千上万。千百年来,他收集了各式各样的灵魂,包括诗人、牧师、帝王、乞丐、奴隶、罪人,当然,还有英雄。
每一个灵魂,都有着自己的战斗力,而影魔,靠这些战斗力提升自己的攻击。
奈文摩尔有 n n n 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 1 1 1 到 n n n。第 i i i 个灵魂的战斗力为 k i k_i ki,灵魂们以点对的形式为影魔提供攻击力,对于灵魂对 i , j ( i < j ) i, j(i<j) i,j(i<j) 来说,若不存在 k s ( i < s < j ) k_s(i<s<j) ks(i<s<j) 大于 k i k_i ki 或者 k j k_j kj,则会为影魔提供 p 1 p_1 p1 的攻击力(可理解为:当 j = i + 1 j = i + 1 j=i+1 时,因为不存在满足 i < s < j i < s < j i<s<j 的 s s s,从而 k s k_s ks 不存在,这时提供 p 1 p_1 p1 的攻击力;当 j > i + 1 j > i + 1 j>i+1 时,若 max { k s ∣ i < s < j } ≤ min ( k i , k j ) \max\{k_s | i < s < j\}\le \min(k_i, k_j) max{ks∣i<s<j}≤min(ki,kj),则提供 p 1 p_1 p1 的攻击力);另一种情况,令 c c c 为 k i + 1 , k i + 2 , ⋯   , k j − 1 k_{i + 1}, k_{i + 2}, \cdots, k_{j -1} ki+1,ki+2,⋯,kj−1 的最大值,若 c c c 满足: k i < c < k j k_i < c < k_j ki<c<kj,或者 k j < c < k i k_j < c < k_i kj<c<ki,则会为影魔提供 p 2 p_2 p2 的攻击力,当这样的 c c c 不存在时,自然不会提供这 p 2 p_2 p2 的攻击力;其他情况的点对,均不会为影魔提供攻击力。
影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间 [ a , b ] [a,b] [a,b],位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑所有满足 a ≤ i < j ≤ b a\le i<j\le b a≤i<j≤b 的灵魂对 i , j i, j i,j 提供的攻击力之和。
顺带一提,灵魂的战斗力组成一个 1 1 1 到 n n n 的排列: k 1 , k 1 , ⋯   , k n k_1, k_1, \cdots, k_n k1,k1,⋯,kn。
Input Format
第一行四个整数 n , m , p 1 , p 2 n,m,p_1,p_2 n,m,p1,p2;
第二行 n n n 个整数数: k 1 , k 2 , ⋯   , k n k_1, k_2,\cdots, k_n k1,k2,⋯,kn;
接下来 m m m 行,每行两个数 a , b a,b a,b,表示询问区间 [ a , b ] [a,b] [a,b] 中的灵魂对会为影魔提供多少攻击力。
Output Format
共输出 m m m 行,每行一个答案,依次对应 m m m 个询问。
Sample
Input
10 5 2 3
7 9 5 1 3 10 6 8 2 4
1 7
1 9
1 3
5 9
1 5
Output
30
39
4
13
16
Range
对于 30 % 30\% 30% 的数据, 1 ≤ n , m ≤ 500 1\le n, m\le 500 1≤n,m≤500;
另有 30 % 30\% 30% 的数据, p 1 = 2 p 2 p_1 = 2p_2 p1=2p2;
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 200000 , 1 ≤ p 1 , p 2 ≤ 1000 1\le n,m\le 200000, 1\le p_1, p_2\le 1000 1≤n,m≤200000,1≤p1,p2≤1000。
Algorithm
线段树
Mentality
蛮简单的一道题吧 . . . . . . ...... ......
不难想到,我们应该首先求出两个数组 l l [ i ] , r r [ i ] ll[i],rr[i] ll[i],rr[i] 分别代表 i i i 左边第一个比 a [ i ] a[i] a[i] 大的数的位置和右边第一个比 a [ i ] a[i] a[i] 大的数的位置。
具体怎么求呢?维护一个单调递减的单调栈就好了。详见代码。
然后我们发现,对于每个区间 [ i , i + 1 ] [i,i+1] [i,i+1] ,它们必定都有 p 1 p1 p1 的贡献,这个其实在输入询问的时候就可以处理了,即 a n s + = ( r − l ) ∗ p 1 ans+=(r-l)*p1 ans+=(r−l)∗p1 。
那么不难发现对于一个位置 i i i ,它对区间 [ l l [ i ] + 1 ∼ i − 1 , r r [ i ] ] [ll[i]+1\sim i-1,rr[i]] [ll[i]+1∼i−1,rr[i]] 和区间 [ l l [ i ] , i + 1 ∼ r r [ i ] − 1 ] [ll[i],i+1\sim rr[i]-1] [ll[i],i+1∼rr[i]−1] 都能产生 p 2 p2 p2 的贡献,那我们只需要将每个点 i i i 挂载在 l l [ i ] , r r [ i ] ll[i],rr[i] ll[i],rr[i] 两个位置上,一旦扫到 l l [ i ] ll[i] ll[i] ,就将 [ i + 1 , r r [ i ] ] [i+1,rr[i]] [i+1,rr[i]] 这段区间都加上 p 2 p2 p2 ,扫到 r r [ i ] rr[i] rr[i] ,就将 l l [ i ] ll[i] ll[i] 位置加上 p 1 p1 p1 ,将 [ l l [ i ] + 1 , i − 1 ] [ll[i]+1,i-1] [ll[i]+1,i−1] 这段区间都加上 p 2 p2 p2 即可统计区间贡献。
那么如何统计答案呢?其实很简单,对于每个询问区间 [ l , r ] [l,r] [l,r] ,我们只需要保证统计的值全部都来自询问区间内的点对即可。那么换句话说,我们可以用总贡献减去不属于询问区间的贡献。
即我们在扫到某个询问左端点前一个位置 l − 1 l-1 l−1 时,记录下 [ l , r ] [l,r] [l,r] 的贡献和 s u m 1 sum1 sum1 ,这些就是不属于询问区间的贡献,而扫到右端点 r r r 时记录下贡献和 s u m 2 sum2 sum2 ,这些就是总贡献,那么答案就是 s u m 2 − s u m 1 sum2-sum1 sum2−sum1 。
很简单伐。
注意事项:
-
那些 [ i , i + 1 ] [i,i+1] [i,i+1] 的小贡献要加上。
-
对于一个点 i i i ,若左边或右边没有更大的了,将贡献区间左右端点挂载 0 0 0 和 n + 1 n+1 n+1 上,因为这种情况并不能产生贡献。
Code
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ls (o<<1)
#define rs ((o<<1)+1)
#define mid ((l+r)>>1)
int n,m,p1,p2,a[200001],ql[200001],qr[200001];
int top,L,R,x,stack[200001],rr[200001],ll[200001];
long long ans,sum[800005],adv[800005],sum1[200001],sum2[200001];
vector <int> Mount[5][200005];
void pushdown(int o,int l,int r)
{
adv[ls]+=adv[o],adv[rs]+=adv[o];
sum[ls]+=1ll*(mid-l+1)*adv[o];
sum[rs]+=1ll*(r-mid)*adv[o];
adv[o]=0;
}
void add(int o,int l,int r)
{
if(L>R)return;
if(l>=L&&r<=R)
{
sum[o]+=1ll*(r-l+1)*x,adv[o]+=x;
return;
}
pushdown(o,l,r);
if(mid>=L)add(ls,l,mid);
if(mid<R)add(rs,mid+1,r);
sum[o]=sum[ls]+sum[rs];
}
void query(int o,int l,int r)
{
if(l>=L&&r<=R)
{
ans+=sum[o];
return;
}
pushdown(o,l,r);
if(mid>=L)query(ls,l,mid);
if(mid<R)query(rs,mid+1,r);
sum[o]=sum[ls]+sum[rs];
}
void Add(int l,int r,int w)
{
L=l,R=r,x=w;
add(1,0,n);
}
void Query(int l,int r)
{
L=l,R=r,ans=0;
query(1,0,n);
}
int main()
{
freopen("3722.in","r",stdin);
freopen("3722.out","w",stdout);
cin>>n>>m>>p1>>p2;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
stack[top=0]=0;//初始值设为 0 和 n+1
for(int i=1;i<=n;i++)
{
while(top>0&&a[stack[top]]<a[i])top--;
ll[i]=stack[top],stack[++top]=i;
}//单调栈预处理 ll,rr 数组
stack[top=0]=n+1;
for(int i=n;i>=1;i--)
{
while(top>0&&a[stack[top]]<a[i])top--;
rr[i]=stack[top],stack[++top]=i;
}
for(int i=1;i<=n;i++)
{
Mount[1][rr[i]].push_back(i);
Mount[2][ll[i]].push_back(i);
}//挂载贡献区间
int l,r;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&ql[i],&qr[i]);
sum2[i]+=1ll*(qr[i]-ql[i])*p1;
Mount[3][ql[i]-1].push_back(i);
Mount[4][qr[i]].push_back(i);
}//挂载询问端点
for(int i=1;i<=n;i++)
{
for(int j=0,limit=Mount[1][i].size();j<limit;j++)
{
int to=Mount[1][i][j];
Add(ll[to],ll[to],p1);
Add(ll[to]+1,to-1,p2);
}
for(int j=0,limit=Mount[2][i].size();j<limit;j++)
{
int to=Mount[2][i][j];
Add(to+1,rr[to]-1,p2);
}
for(int j=0,limit=Mount[3][i].size();j<limit;j++)
{
int to=Mount[3][i][j];
Query(ql[to],qr[to]);
sum1[to]=ans;
}
for(int j=0,limit=Mount[4][i].size();j<limit;j++)
{
int to=Mount[4][i][j];
Query(ql[to],qr[to]);
sum2[to]+=ans;
}
}
for(int i=1;i<=m;i++)
printf("%lld\n",sum2[i]-sum1[i]);
}