【HNOI 2017】影魔

本文介绍了一种算法问题,涉及计算特定条件下灵魂对提供的攻击力总和。通过预处理和使用线段树数据结构,有效地解决了区间查询问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem

Description

影魔,奈文摩尔,据说有着一个诗人的灵魂。事实上,他吞噬的诗人灵魂早已成千上万。千百年来,他收集了各式各样的灵魂,包括诗人、牧师、帝王、乞丐、奴隶、罪人,当然,还有英雄。

每一个灵魂,都有着自己的战斗力,而影魔,靠这些战斗力提升自己的攻击。

奈文摩尔有 nnn 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 111nnn。第 iii 个灵魂的战斗力为 kik_iki,灵魂们以点对的形式为影魔提供攻击力,对于灵魂对 i,j(i&lt;j)i, j(i&lt;j)i,j(i<j) 来说,若不存在 ks(i&lt;s&lt;j)k_s(i&lt;s&lt;j)ks(i<s<j) 大于 kik_iki 或者 kjk_jkj,则会为影魔提供 p1p_1p1 的攻击力(可理解为:当 j=i+1j = i + 1j=i+1 时,因为不存在满足 i&lt;s&lt;ji &lt; s &lt; ji<s<jsss,从而 ksk_sks 不存在,这时提供 p1p_1p1 的攻击力;当 j&gt;i+1j &gt; i + 1j>i+1 时,若 max⁡{ks∣i&lt;s&lt;j}≤min⁡(ki,kj)\max\{k_s | i &lt; s &lt; j\}\le \min(k_i, k_j)max{ksi<s<j}min(ki,kj),则提供 p1p_1p1 的攻击力);另一种情况,令 cccki+1,ki+2,⋯&ThinSpace;,kj−1k_{i + 1}, k_{i + 2}, \cdots, k_{j -1}ki+1,ki+2,,kj1 的最大值,若 ccc 满足:ki&lt;c&lt;kjk_i &lt; c &lt; k_jki<c<kj,或者 kj&lt;c&lt;kik_j &lt; c &lt; k_ikj<c<ki,则会为影魔提供 p2p_2p2 的攻击力,当这样的 ccc 不存在时,自然不会提供这 p2p_2p2 的攻击力;其他情况的点对,均不会为影魔提供攻击力。

影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间 [a,b][a,b][a,b],位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑所有满足 a≤i&lt;j≤ba\le i&lt;j\le bai<jb 的灵魂对 i,ji, ji,j 提供的攻击力之和。

顺带一提,灵魂的战斗力组成一个 111nnn 的排列:k1,k1,⋯&ThinSpace;,knk_1, k_1, \cdots, k_nk1,k1,,kn

Input Format

第一行四个整数 n,m,p1,p2n,m,p_1,p_2n,m,p1,p2

第二行 nnn 个整数数:k1,k2,⋯&ThinSpace;,knk_1, k_2,\cdots, k_nk1,k2,,kn

接下来 mmm 行,每行两个数 a,ba,ba,b,表示询问区间 [a,b][a,b][a,b] 中的灵魂对会为影魔提供多少攻击力。

Output Format

共输出 mmm 行,每行一个答案,依次对应 mmm 个询问。

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≤5001\le n, m\le 5001n,m500

另有 30%30\%30% 的数据,p1=2p2p_1 = 2p_2p1=2p2

对于 100%100\%100% 的数据,1≤n,m≤200000,1≤p1,p2≤10001\le n,m\le 200000, 1\le p_1, p_2\le 10001n,m200000,1p1,p21000

Algorithm

线段树

Mentality

蛮简单的一道题吧 ..................

不难想到,我们应该首先求出两个数组 ll[i],rr[i]ll[i],rr[i]ll[i],rr[i] 分别代表 iii 左边第一个比 a[i]a[i]a[i] 大的数的位置和右边第一个比 a[i]a[i]a[i] 大的数的位置。

具体怎么求呢?维护一个单调递减的单调栈就好了。详见代码。

然后我们发现,对于每个区间 [i,i+1][i,i+1][i,i+1] ,它们必定都有 p1p1p1 的贡献,这个其实在输入询问的时候就可以处理了,即 ans+=(r−l)∗p1ans+=(r-l)*p1ans+=(rl)p1

那么不难发现对于一个位置 iii ,它对区间 [ll[i]+1∼i−1,rr[i]][ll[i]+1\sim i-1,rr[i]][ll[i]+1i1,rr[i]] 和区间 [ll[i],i+1∼rr[i]−1][ll[i],i+1\sim rr[i]-1][ll[i],i+1rr[i]1] 都能产生 p2p2p2 的贡献,那我们只需要将每个点 iii 挂载在 ll[i],rr[i]ll[i],rr[i]ll[i],rr[i] 两个位置上,一旦扫到 ll[i]ll[i]ll[i] ,就将 [i+1,rr[i]][i+1,rr[i]][i+1,rr[i]] 这段区间都加上 p2p2p2 ,扫到 rr[i]rr[i]rr[i] ,就将 ll[i]ll[i]ll[i] 位置加上 p1p1p1 ,将 [ll[i]+1,i−1][ll[i]+1,i-1][ll[i]+1,i1] 这段区间都加上 p2p2p2 即可统计区间贡献。

那么如何统计答案呢?其实很简单,对于每个询问区间 [l,r][l,r][l,r] ,我们只需要保证统计的值全部都来自询问区间内的点对即可。那么换句话说,我们可以用总贡献减去不属于询问区间的贡献。

即我们在扫到某个询问左端点前一个位置 l−1l-1l1 时,记录下 [l,r][l,r][l,r] 的贡献和 sum1sum1sum1 ,这些就是不属于询问区间的贡献,而扫到右端点 rrr 时记录下贡献和 sum2sum2sum2 ,这些就是总贡献,那么答案就是 sum2−sum1sum2-sum1sum2sum1

很简单伐。

注意事项:

  • 那些 [i,i+1][i,i+1][i,i+1] 的小贡献要加上。

  • 对于一个点 iii ,若左边或右边没有更大的了,将贡献区间左右端点挂载 000n+1n+1n+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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值