题目描述
有一个长度为n的排列a[],定义一个点对(i,j) (i < j)的贡献:
- 若 max(a[i+1 j−1])<min(a[i],a[j]) ,贡献p1
- 若
sort(a[i],a[j],max(a[i+1 j−1]))
后,
max(a[i+1 j−1])
为第二项,贡献p2
给m个询问,每次询问一个区间[l,r]的贡献和。
30%:1<= n,m <= 500。
另30%: p1=2*p2。
100%:1 <= n,m <= 200000;1 <= p1,p2 <= 1000。
分析
我们可以先从第二档分析一下。
p1=2*p2提示我们拆开p1,把情况1转化为2,怎么弄呢?
先看看情况2吧。考虑i作为左端点(右端点相似),对于一个点对(i,j),如果这
max(a[i+1 j−1])
小于
a[i]
,而且而且
a[j]
小于
max(a[i+1 j−1])
,则贡献。那么如果把后面那个条件去掉,在考虑i做完两个端点后,某些点对(i,j)被算了两次,而被算重的点对,恰好就是能贡献p1的。
那么做法出来了:
对于i作为左端点。
1. 用单调栈弄出每一个点i向右跳的第一个比他大的点,记为next[i];
2. 离线挂询问从右到左扫,把点i为左端点的贡献记在右端点上,即线段树给[i+1,next[i]]打上+p2标记。
3. 对于左端点在i的询问(l,r),查询[l,r]的权值和。
4. i为右端点的话就翻转所有下标再做一次就好了。
那么满分怎么做呢?考虑到(i,next[i])构成了贡献p1的点对,而又被加了两次的p2,那么用刚刚的算法,每次做到i,就给贡献数组的next[i]那一位加上p1-2*p2,注意到同一点对p1不会加两次。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=200010;
struct rec
{
int l,r,id;
}que[N];
struct seg
{
ll sum,tag;
}tr[N*4];
int n,m,p1,p2,a[N],k,l[N],r[N],next[N],sta[N],st,le,i,j;
ll prt[N];
bool cmp(rec a,rec b)
{
return a.l>b.l;
}
void down(int x,int l,int r)
{
int m=(l+r)/2;
tr[x].sum+=tr[x].tag*((ll)r-l+1);
if (l!=r)
{
tr[x*2].tag+=tr[x].tag;
tr[x*2+1].tag+=tr[x].tag;
}
tr[x].tag=0;
}
void change(int x,int l,int r,int i,int j,int val)
{
if (i>j) return;
int m=(l+r)/2;
down(x,l,r);
if (l!=r)
{
down(x*2,l,m);
down(x*2+1,m+1,r);
}
if (l==i&&r==j)
{
tr[x].tag+=val;
down(x,l,r);
return;
}
if (j<=m) change(x*2,l,m,i,j,val);else
if (m<i) change(x*2+1,m+1,r,i,j,val);else
{
change(x*2,l,m,i,m,val);
change(x*2+1,m+1,r,m+1,j,val);
}
tr[x].sum=tr[x*2].sum+tr[x*2+1].sum;
}
ll get(int x,int l,int r,int i,int j)
{
if (i>j) return 0;
int m=(l+r)/2;
down(x,l,r);
if (l!=r)
{
down(x*2,l,m);
down(x*2+1,m+1,r);
}
if (l==i&&r==j) return tr[x].sum;
if (j<=m) return get(x*2,l,m,i,j);else
if (m<i) return get(x*2+1,m+1,r,i,j);else
return get(x*2,l,m,i,m)+get(x*2+1,m+1,r,m+1,j);
tr[x].sum=tr[x*2].sum+tr[x*2+1].sum;
}
void solve()
{
fo(i,1,m) que[i].l=l[i],que[i].r=r[i],que[i].id=i;
sort(que+1,que+1+m,cmp);
a[n+1]=1e9;
sta[(st=1)]=n+1;
fd(i,n,1)
{
while (st&&a[sta[st]]<a[i]) st--;
next[i]=sta[st];
sta[++st]=i;
}
le=1;
fd(i,n,1)
{
change(1,1,n+1,i+1,next[i],+p2);
change(1,1,n+1,next[i],next[i],p1-2*p2);
while (le<=m&&que[le].l==i)
{
prt[que[le].id]+=get(1,1,n+1,i,que[le].r);
le++;
}
if (le>m) break;
}
}
int main()
{
freopen("sf.in","r",stdin);
// freopen("sf.out","w",stdout);
scanf("%d %d %d %d",&n,&m,&p1,&p2);
fo(i,1,n) scanf("%d",a+i);
fo(i,1,m) scanf("%d %d",l+i,r+i);
solve();
fo(i,1,(n+1)*4) tr[i].sum=tr[i].tag=0;
fo(i,1,n/2) swap(a[i],a[n-i+1]);
fo(i,1,m)
{
l[i]=n-l[i]+1,r[i]=n-r[i]+1;
swap(l[i],r[i]);
}
solve();
fo(i,1,m) printf("%lld\n",prt[i]);
}