题目描述
奈文摩尔有 n 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 1 到 n。第 i 个灵魂的战斗力为 ki ,灵魂们以点对的形式为影魔提供攻击力。对于灵魂对 i,j (i<j) 来说,若不存在 ks (i<s<j) 大于 ki 或者 kj ,则会为影魔提供 p1的攻击力。另一种情况,令 c 为 ki+1, ki+2, ……, kj -1 的最大值,若 c 满足:ki < c < kj ,或者 kj < c < ki ,则会为影魔提供 p2的攻击力,当这样的 c 不存在时,自然不会提供这 p2的攻击力;其他情况的点对,均不会为影魔提供攻击力。
影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间 [a,b],位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑所有满足 a≤i<j≤b 的灵魂对 i,j 提供的攻击力之和。
顺带一提,灵魂的战斗力组成一个 1 到 n 的排列:k1, k2 ,⋯,kn。
输入格式
第一行四个整数 n,m,p1,p2。
第二行 n 个整数 k1,k2,⋯,kn。
接下来 mm 行,每行两个数 a,ba,b,表示询问区间 [a,b][a,b] 中的灵魂对会为影魔提供多少攻击力。
输出格式
共输出 m 行,每行一个答案,依次对应 m 个询问。
输入输出样例
输入
10 5 2 3
7 9 5 1 3 10 6 8 2 4
1 7
1 9
1 3
5 9
1 5
输出
30
39
4
13
16
说明/提示
对于 30% 的数据,1≤n,m≤500。
另有 30% 的数据,p1 = 2p。
对于 100% 的数据,1≤n,m≤200000,1≤p1,p2≤1000。
题目分析
要考虑这两种贡献,我们可以分成两种情况:对于区间[l,r]
1)r-l==1时,也就是说区间(l,r)为空,此时k[l]和k[r]一定是大于 (l,r) 中的所有数的,因此一点可以提供p1的贡献。
2)r-l>1时,(l,r) 中一定有唯一的最大值,我们设最大值在 i 处,我们可以枚举所有的 i ,看看它会对那些区间产生影响。
我们设l[i]为i左边第一个大于k[i]的数,r[i]为i右边第一个大于k[i]的数。
这两个数组我们可以用单调栈直接求出来。
- 若i为 (l,r) 中的最大值,并且区间 [l,r] 产生了p1的贡献,那么说明 l[i]=l,r[i]=r。因为如果 l > l[i] ,那么说明 k[l] < k[i],因此该区间无法提供p1的贡献;而如果 l < l[i],那么说明 k[l[i]] > l[i],这样i就不是(l,r)的最大值了,也无法提供p1的贡献。右端点同理。
- 若i为 (l,r) 中的最大值,并且区间 [l,r] 产生了p2的贡献,那么有两种情况:
(1)l=l[i],r<r[i],也就是说形成了 k[l] > k[i] > k[r] 的形式,那么此时对于i来说,区间 [l, [i+1,r]] 都可以产生p2的贡献。
(2)l>l[i],r=r[i],也就是说形成了 k[r] > k[i] > k[l] 的形式,那么此时对于i来说,区间 [[l,i-1], r] 都可以产生p2的贡献。
综上,对于区间l[i]到r[i]有p1的贡献.
对于左端点在l[i]+1到i-1,右端点为R[i]的区间有p2的贡献.
对于左端点为l[i],右端点为i+1到r[i]-1的区间也有p2的贡献.
然后我们可以从左到右进行枚举,
对于情况1来说,当我们扫到 r[i] 时,更新l[i]的贡献。
对于情况2.1来说,当我们扫到 l[i] 时,更新区间 [i+1,r[i]-1] 的贡献。
对于情况2.2来说,我们在扫到 r[i] 时,更新区间 [l[i]+1,i-1] 的贡献。
对于区间的加数与求和操作,我们用树状数组即可完成。
代码如下
#include <iostream>
#include <cmath>
#include <cstdio>
#include <set>
#include <unordered_set>
#include <string>
#include <cstring>
#include <map>
#include <unordered_map>
#include <algorithm>
#include <stack>
#include <queue>
#include <bitset>
#define LL long long
#define PII pair<int,int>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=4e5+5,INF=0x3f3f3f3f;
struct Node{ //记录查询区间
int l,r,x,id,v;
bool operator< (const Node &a)const
{ return x<a.x; }
}a[N],b[N];
int k[N]; //题目中的k[i]数组
int st[N],top; //单调栈
int l[N],r[N];
LL tr1[N],tr2[N],ans[N]; //树状数组与答案数组
int lowbit(int x) //以下三个函数是树状数组的模板函数
{
return x&-x;
}
void add(int x,int c)
{
for(int i=x;i<N;i+=lowbit(i))
tr1[i]+=c,tr2[i]+=(LL)c*x;
}
LL sum(int x)
{
LL res=0;
for(int i=x;i;i-=lowbit(i))
res+=(LL)tr1[i]*(x+1)-tr2[i];
return res;
}
int main()
{
int n,m,p1,p2;
scanf("%d%d%d%d",&n,&m,&p1,&p2);
k[0]=k[n+1]=n+1; top=1; //在0和n+1位置放两个哨兵,防止预处理l[i]和r[i]时出现越界
for(int i=1;i<=n;i++) scanf("%d",&k[i]);
for(int i=1;i<=n+1;i++) //预处理l[i]和r[i]
{
while(k[st[top]]<k[i]) r[st[top]]=i,top--;
l[i]=st[top];
st[++top]=i;
}
for(int i=1;i<=m;i++) //离线算法,用a[i]记录询问区间
{
int l,r;
scanf("%d%d",&l,&r);
ans[i]=(r-l)*p1; //对于区间[l,r]来说,可以贡献r-l个长度为2的区间
a[i]={l,r,l-1,i,-1};
a[i+m]={l,r,r,i,1};
}
sort(a+1,a+1+m*2);
int tot=0;
for(int i=1;i<=n;i++) //用b[]记录贡献区间,扫到x时给[l,r]都加v
{
if(1<=l[i]&&r[i]<=n) b[++tot]={l[i],l[i],r[i],0,p1};
if(1<=l[i]&&r[i]>i+1) b[++tot]={i+1,r[i]-1,l[i],0,p2};
if(i-1>l[i]&&r[i]<=n) b[++tot]={l[i]+1,i-1,r[i],0,p2};
}
sort(b+1,b+1+tot);
int n1=1,n2=1;
while(!a[n1].x) n1++; //过滤掉x为0的a[]
for(int i=1;n1<=m*2&&i<=n;i++)
{
while(n2<=tot&&b[n2].x==i) //当扫描到b[].x时,加上该贡献
{
add(b[n2].l,b[n2].v); //给[l,r]都加上v
add(b[n2].r+1,-b[n2].v);
n2++;
}
while(n1<=m*2&&a[n1].x==i) //当扫描到a[].x时,算出该询问
{
ans[a[n1].id]+=a[n1].v*(sum(a[n1].r)-sum(a[n1].l-1));
n1++;
}
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]); //输出答案
return 0;
}