[Luogu3722] [AH2017/HNOI2017] 影魔 [扫描线]

Link
https://www.luogu.org/problemnew/show/P3722


题意
给出 n n n 个整数 k i , i = 1 ⋯ n k_i,i=1\cdots n ki,i=1n 它们组成一个 1 ∼ n 1\sim n 1n 的排列
对于点对 ( a , b ) (a,b) (a,b) 如果 max ⁡ i ∈ ( a , b ) k i ≤ max ⁡ { k a , k b } \max\limits_{i\in(a,b)}k_i\le\max\{k_a,k_b\} i(a,b)maxkimax{ka,kb} 那么产生贡献 p 1 p_1 p1
如果 max ⁡ i ∈ ( a , b ) k i ∈ ( min ⁡ { k a , k b } , max ⁡ { k a , k b } ) \max\limits_{i\in(a,b)} k_i\in(\min\{k_a,k_b\},\max\{k_a,k_b\}) i(a,b)maxki(min{ka,kb},max{ka,kb}) 那么产生贡献 p 2 p_2 p2
询问 m m m 次:每次询问一段区间 [ L , R ] [L,R] [L,R] 内点对贡献的和
1 ≤ n , m ≤ 2 × 1 0 5 , 1 ≤ p 1 , p 2 ≤ 1 0 3 1\le n,m\le2\times10^5,1\le p_1,p_2\le10^3 1n,m2×105,1p1,p2103


说是点对产生贡献,但是点对中间夹了一大堆东西,特别是最大值尤其突出啊。
在这里要注意一点,我们下面的考虑都是基于有“中间”的情况,所以统计贡献要强加(i,j)相邻的贡献
我们考虑每个点在哪个范围内能够作为最大值,然后计算它的贡献
这个东西你可以开一个单调栈扫两遍,也可以直接扫一遍(%%%)
对于 i i i 我们假设它两边第一个比它大的数位置分别是 l i , r i l_i,r_i li,ri
考虑离线……
① 显然 i i i [ l i , r i ] [l_i,r_i] [li,ri] 贡献为 p 1 p_1 p1
② 对 [ l i + 1 ∼ i − 1 , r i ] [l_i+1\sim i-1,r_i] [li+1i1,ri] 贡献为 p 2 p_2 p2
③ 对 [ l i , i + 1 ∼ r i − 1 ] [l_i,i+1\sim r_i-1] [li,i+1ri1] 贡献为 p 2 p_2 p2
期望扫一遍能够得到贡献,那么应该是对每个询问统计 [ L , R ] [L,R] [L,R] R R R 产生的贡献和减掉到 L − 1 L-1 L1 产生的贡献和
然后我就嘴巴AC这道题啦 嘴它!


具体要怎么维护呢?
我们考虑开一个数据结构扫 Timeline 的话……
① 那么它必须只在 l i ∼ r i l_i\sim r_i liri 的时间内产生一次贡献,而且必须给且只给 [ l i , r i ] [l_i,r_i] [li,ri] 产生
考虑安排在 r i r_i ri 产生贡献,给 l i l_i li 产生然后在 l i + 1 l_{i+1} li+1 消失
就杠杠好……反过来也可以,反正就是限制一下更小的区间不会统计到这个贡献即可
② 同理,为了方便在 r i r_i ri ( l i , i ) (l_i,i) (li,i)
③ 同理,为了方便在 l i l_i li ( i , r i ) (i,r_i) (i,ri)
用树壮数组或者线duang树维护同一时刻里面的{贡献(按时间的)前缀和}的前缀和就好了。


ps
壮壮数组写起来好壮壮啊
有点毒瘤
结果并没有卡常 那我干什么不用线段树秒


其实是道水题啊
我爆炸弱了(


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<ctime>
#include<cmath>
using namespace std;
const int MAXN = 4e5;
int n, m, p1, p2, tot;
int k[MAXN], stk[MAXN];
long long c1[MAXN], c2[MAXN];
int L[MAXN], R[MAXN];
struct Opt
{
	int l, r, x, id, value;
	inline Opt(const int&a=0, const int&b=0, const int&c=0, const int&d=0, const int&e=0)
	{
		l = a, r = b, x = c, id = d, value = e;
	}
	inline bool operator < (const Opt& o) const
	{
		return x < o.x;
	}
}Needle[2*MAXN], Noodle[3*MAXN];
int NeedleTot, NoodleTot;
long long Ans[MAXN];
inline long long Query(int Pos)
{
	long long Ans = 0;
	int Tmp = Pos; //Attention
	while (Pos)
	{
		Ans += 1ll * (Tmp + 1) * c1[Pos] - c2[Pos];
		Pos -= Pos & -Pos;
	}
	return Ans;
}
inline void Modify(int Pos, const int& x)
{
	int Tmp = Pos; //Attention
	while (Pos <= n)
	{
		c1[Pos] += x;
		c2[Pos] += 1ll * Tmp * x; 
		Pos += Pos & -Pos;
	}
}
int main()
{
	scanf("%d%d%d%d", &n, &m, &p1, &p2);
	for (register int i = 1; i <= n; ++i) scanf("%d", &k[i]);
	k[0] = k[n+1] = n+1;
	++stk[0];
	for (register int i = 1; i <= n + 1; ++i)
	{
		while (k[stk[stk[0]]]<k[i]) R[stk[stk[0]]] = i, --stk[0];
		L[i] = stk[stk[0]];
		stk[++stk[0]] = i;
	}
	for (register int Lefta, Rightb, i = 1; i <= m; ++i)
	{
		scanf("%d%d", &Lefta, &Rightb);
		Ans[i] = 1ll * (Rightb - Lefta) * p1;
		if (Lefta > 1) Needle[++NeedleTot] = Opt(Lefta, Rightb, Lefta-1, i, -1);
		Needle[++NeedleTot] = Opt(Lefta, Rightb, Rightb, i, 1);
	}
	sort(Needle + 1, Needle + 1 + NeedleTot);
	for (register int i = 1; i <= n; ++i)
	{
		if (1 <= L[i] && R[i] <= n) Noodle[++NoodleTot] = Opt(L[i], L[i], R[i], 0, p1);
		if (L[i] < i-1 && R[i] <= n) Noodle[++NoodleTot] = Opt(L[i]+1, i-1, R[i], 0, p2);
		if (R[i] > i+1 && L[i] >= 1) Noodle[++NoodleTot] = Opt(i+1, R[i]-1, L[i], 0, p2);
	}
	sort(Noodle + 1, Noodle + NoodleTot + 1);
	int PointerQuer = 1, PointerMod = 1;
	for (register int Pos = 1; Pos <= n && PointerQuer <= NeedleTot; ++Pos)
	{
		while (PointerMod <= NoodleTot && Noodle[PointerMod].x == Pos)
		{
			Modify(Noodle[PointerMod].l, Noodle[PointerMod].value);
			Modify(Noodle[PointerMod].r + 1, -Noodle[PointerMod].value);
			++PointerMod;
		}
		while (PointerQuer <= NeedleTot && Needle[PointerQuer].x == Pos)
		{
			Ans[Needle[PointerQuer].id] += (Query(Needle[PointerQuer].r) - Query(Needle[PointerQuer].l-1)) * Needle[PointerQuer].value;
			++PointerQuer;
		}
	}
	for (register int i = 1; i <= m; ++i) printf("%lld\n", Ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值