【题解】 「AHOI / HNOI2017」影魔 线段树+单调栈+扫描线+差分

Legend

Link \(\textrm{to LOJ}\)。

Editorial

题意翻译之后就是有两个贡献方式:

  • 区间端点为最大和次大时 \(+p_1\)。
  • 区间端点为最大和非次大时 \(+p_2\)。

手玩一下就感觉这个贡献方式与单调栈出奇地像,不妨就用单调栈的角度来分析这个贡献。

先求出每个区间的贡献是 \(p_1,p_2,0\) 中的哪一种。

 #
 # #
 # #          #
 # #   #      # <-New
 # #   #     ##
 # #   #     ##
===============
010122212222210

考虑上图这个例子,# 的个数表示数字大小,下方的数字表示以 \(New\) 为右端点,每个左端点的答案。

容易发现以下几个性质:

  • 之前位于单调栈中的元素所在位置的贡献均为 \(p_1\)。
  • 以单调栈中第一个大于 \(New\) 的元素为分界线,左侧不在单调栈中的位置贡献为 \(0\),右侧为 \(p_2\)。

第一个贡献非常好做:我们求出每一个元素在取哪些右端点的时候存在,这显然是一个区间 \([st,end]\)。算这个贡献可以直接扫描线差分。

这部分的复杂度是 \(O(n \log n)\)。

第二个贡献也比较好做:对于单调栈中相邻两个元素,记录在取哪些右端点的时候存在,这显然也是一个区间 \([st,end]\),依然扫描线解决这个贡献。

因为单调栈每一个元素只会插入弹出一次,所以“相邻两个元素”的数量是 \(O(n)\) 的,复杂度也是 \(O(n \log n)\) 的。

这样子,我们通过扫描线可以求出一个 \(n \times n\) 的一个矩阵,其中位于 \((i,j)\ (i < j)\) 的数字代表右端点取 \(i\),左端点取 \(j\) 时候的贡献。

例如,这是样例的矩阵:

R\L 1   2   3   4   5   6   7   8   9   10
1   0   0   0   0   0   0   0   0   0   0
2   2   0   0   0   0   0   0   0   0   0
3   0   2   0   0   0   0   0   0   0   0
4   0   3   2   0   0   0   0   0   0   0
5   0   3   2   2   0   0   0   0   0   0
6   3   2   2   3   2   0   0   0   0   0
7   0   0   0   0   0   2   0   0   0   0
8   0   0   0   0   0   2   2   0   0   0
9   0   0   0   0   0   3   0   2   0   0
10  0   0   0   0   0   3   0   2   2   0

而我们对于 \([l,r]\) 的一次询问,本质就是求矩形 \((l,l)-(r,r)\) 的元素和,这可以一起扫描线+差分解决。


具体怎么差分呢?我们可以分析一下查询矩形和修改矩形的关系。

修改矩形已经被我们在横坐标上差分了,即变成了左侧的一个 \(+v\) 线段和右侧的一个 \(-v\) 线段。

查询矩形也被我们在横坐标上差分了,变成了右侧的前缀和减左侧的前缀和。

现在单独考虑怎么求一个前缀和:即看修改矩形横坐标与当前查询位置的距离。

假设修改矩形位于 \(x_1\),权值为 \(v\) ,查询位于 \(x_2\),则对于某一行,它的贡献就是 \((x_2-x_1+1)v=x_2v-x_1v+v\)。

不难发现我们可以维护 \(\sum (1-x_1)v\) 和 \(\sum v\) 的值,就可以求出来了。

总复杂度 \(O(n \log n)\),不过常数比较大。

Code

大概还是有些细节,感觉这个差分的套路写起来挺爽的

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 2e5 + 23;
const LL MOD = 998244353;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

LL n ,m ,p1 ,p2;
int p[MX];

int q;
struct QUERY{
	int type ,x ,l ,r;
	LL id;
}Q[MX * 10];

bool cmp(QUERY A ,QUERY B){
	if(A.x != B.x) return A.x < B.x;
	return A.type < B.type;
}

struct node{
	int l ,r;
	LL add ,sum;
	node *lch ,*rch;
}*R1 ,*R2;

void pushup(node *x){x->sum = x->lch->sum + x->rch->sum;}
void doadd(node *x ,LL v){x->add += v ,x->sum += (x->r - x->l + 1) * v;}

node *build(int l ,int r){
	node *x = new node;
	x->l = l ,x->r = r;
	x->add = x->sum = 0;
	if(l == r){
		x->lch = x->rch = nullptr;
	}
	else{
		int mid = (l + r) >> 1;
		x->lch = build(l ,mid);
		x->rch = build(mid + 1 ,r);
		pushup(x);
	}
	return x;
}

void pushdown(node *x){
	if(x->add){
		doadd(x->lch ,x->add);
		doadd(x->rch ,x->add);
		x->add = 0;
	}
}

void add(node *x ,int l ,int r , LL v){
	if(l <= x->l && x->r <= r) return doadd(x ,v);
	pushdown(x);
	if(l <= x->lch->r) add(x->lch ,l ,r ,v);
	if(r > x->lch->r) add(x->rch ,l ,r ,v);
	return pushup(x);
}

LL sum(node *x ,int l ,int r){
	if(l <= x->l && x->r <= r) return x->sum;
	pushdown(x); LL s = 0;
	if(l <= x->lch->r) s += sum(x->lch ,l ,r);
	if(r > x->lch->r) s += sum(x->rch ,l ,r);
	return s;
}

LL ans[MX];
LL sgn(LL x){
	if(x > 0) return 1;
	if(!x) return 0;
	return -1;
}

int S[100][100];
int stk[MX] ,top;
int main(){
	n = read() ,m = read() ,p1 = read() ,p2 = read();
	for(int i = 1 ; i <= n + 1 ; ++i){
		if(i == 6){
			debug("asDFASDFASDFADS\n");
		}
		if(i <= n) p[i] = read();
		else p[i] = INT_MAX;

		while(top && p[stk[top]] < p[i]){
			if(top > 1) Q[++q] = (QUERY){0 ,i ,stk[top - 1] ,stk[top - 1] ,-p2};
			if(stk[top - 1] + 1 <= stk[top] - 1){
				Q[++q] = (QUERY){0 ,i ,stk[top - 1] + 1 ,stk[top] - 1 ,p2};
				Q[++q] = (QUERY){0 ,i + 1 ,stk[top - 1] + 1 ,stk[top] - 1 ,-p2};
			}

			Q[++q] = (QUERY){0 ,i ,stk[top] ,stk[top] ,p1};
			Q[++q] = (QUERY){0 ,i + 1 ,stk[top] ,stk[top] ,-p1};
			--top;
		}
		stk[++top] = i;
		if(top > 1){
			Q[++q] = (QUERY){0 ,i ,stk[top - 1] ,stk[top - 1] ,p1};
			Q[++q] = (QUERY){0 ,i + 1 ,stk[top - 1] ,stk[top - 1] ,-p1};
			Q[++q] = (QUERY){0 ,i + 1 ,stk[top - 1] ,stk[top - 1] ,p2};
		}
	}

	for(int i = 1 ,l ,r ; i <= m ; ++i){
		l = read() ,r = read();
		Q[++q] = (QUERY){1 ,l - 1 ,l ,r ,-i};
		Q[++q] = (QUERY){1 ,r ,l ,r ,i};
	}

	std::sort(Q + 1 ,Q + 1 + q ,cmp);

	R1 = build(0 ,n + 3);
	R2 = build(0 ,n + 3);
	for(int i = 1 ; i <= q ; ++i){
		if(Q[i].type == 0){
			add(R1 ,Q[i].l ,Q[i].r ,Q[i].id);
			add(R2 ,Q[i].l ,Q[i].r ,(1LL - Q[i].x) * Q[i].id);
		}
		else{
			ans[std::abs(Q[i].id)] += sgn(Q[i].id) * Q[i].x * sum(R1 ,Q[i].l ,Q[i].r);
			ans[std::abs(Q[i].id)] += sgn(Q[i].id) * sum(R2 ,Q[i].l ,Q[i].r);
		}
	}
	for(int i = 1 ; i <= m ; ++i)
		printf("%lld\n" ,ans[i]);
	return 0;

}
上一篇:来学算法 #9 AVL树 (2) : 实现


下一篇:English Word-day01