P3722 [AH2017/HNOI2017]影魔(树状数组)

题目描述

奈文摩尔有 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]的数。这两个数组我们可以用单调栈直接求出来。

  1. 若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的贡献。右端点同理。
  2. 若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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值