2020牛客寒假算法基础集训营4.H——坐火车【树状数组 & 前缀 & 后缀】(超级详细良心题解)

题目传送门


题目描述

牛牛是一名喜欢旅游的同学,在来到渡渡鸟王国时,坐上了颜色多样的火车。
牛牛同学在车上,车上有 n 个车厢,每一个车厢有一种颜色。
他想知道对于每一个正整数 x ∈ [ 1 ,   n ] x \in [1,\ n] x[1, n] ,集合 { ( i ,   x ,   j )   ∣   i < x < j ,   l x ≤ c o l i = c o l j ≤ r x } \{ (i,\ x,\ j)\ |\ i < x < j,\ l_x \le col_i = col_j \le r_x \} {(i, x, j)  i<x<j, lxcoli=coljrx}中包含多少个元素。
换句话说,就是要求每一个车厢两边有多少对颜色相同的车厢,并且这一对车厢的颜色要在 l x l_x lx r x r_x rx 之间。其中 c o l i col_i coli 代表 i 号车厢的颜色, l x l_x lx, r x r_x rx 代表颜色的限制。


输入描述:

第一行一个正整数n。
第二行 n 个三元组,每个三元组包括三个正整数 ( c o l i , l i , r i col_i, l_i, r_i coli,li,ri),输入中没有括号,这 3n 个正整数之间均只用空格隔开,详见样例。


输出描述:

输出一行 n 个非负整数代表答案。


输入

5
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1


输出

0 3 4 3 0


备注:

1 ≤ n ≤ 5 ⋅ 1 0 5 1 \le n \le 5 \cdot 10^5 1n5105

1 ≤ c o l i ,   l i ,   r i ≤ 5 ⋅ 1 0 5 1 \le col_i,\ l_i,\ r_i \le 5 \cdot 10^5 1coli, li, ri5105


题解

  • 首先我们需要理解题意,有点绕。
  • 比如样例: ( 1 , 1 , 1 ) ( 1 , 1 , 1 ) ( 1 , 1 , 1 ) ( 1 , 1 , 1 ) ( 1 , 1 , 1 ) (1,1,1)(1,1,1)(1,1,1)(1,1,1)(1,1,1) (1,1,1)(1,1,1)(1,1,1)(1,1,1)(1,1,1)
    这五个车厢,每个车厢内:中间是 c o l i col_i coli,左右分别是 l i , r i l_i,r_i li,ri 。我们想找的是对于 x ( x ∈ [ 1 , n ] ) x(x\in[1,n]) x(x[1,n]) 车厢,找到左面车厢 a ∈ [ 1 , x − 1 ] a\in[1,x-1] a[1,x1] 和右面车厢 b ∈ [ x + 1 , n ] b\in[x+1,n] b[x+1,n] c o l a = c o l b   且   r x < = c o l a , c o l b < = r x col_a=col_b\ 且\ r_x<=col_a,col_b<=r_x cola=colb  rx<=cola,colb<=rx ,这样的匹配数
  • 知道题目要求什么了之后,我们思考如何去解决这个问题。由于题目比较奇特,先思考暴力的话如何解决这个问题:
    1. 肯定需要枚举 x ∈ [ 1 , n ] x\in[1,n] x[1,n],对于每一个 x x x ,我们可以在 a ∈ [ 1 , x − 1 ] a\in[1,x-1] a[1,x1] 里找一个符合条件的 c o l a col_a cola ,然后从 b ∈ [ x + 1 , n ] b\in[x+1,n] b[x+1,n] 里找颜色相同且符合条件的 c o l b col_b colb,然后 a n s ans ans++。
    2. 重复这个过程,就可以暴力求解了,但是复杂度爆炸。
  • 现在我们来优化这一过程。
  • 一般这样求解区间的情况,最容易想到的是线段树、树状数组、前缀和、dp等
  • 首先感觉和 前 缀 、 后 缀 前缀、后缀 这种操作比较接近(才不是因为题解这么做的
  • 树状数组维护 i i i 位置的   颜 色 对 数 \ 颜色对数   前缀和,换句话说就是对于 a n s [ i ] ans[i] ans[i] ,代表的是 [ 1 , i ] [1,i] [1,i] 这个区间里,符合题目要求的颜色对数
  • 那么对于每一个 x ∈ [ 1 , n ] x\in[1,n] x[1,n] 答案就是 a n s [ r x ] − a n s [ l x − 1 ] ans[r_x] - ans[l_x-1] ans[rx]ans[lx1]
  • 下面考虑如何去维护这一数组
  • 首先头尾肯定是0,因为有一端没有车厢了
  • 我们尝试能否从上一个车厢的答案转移到当前车厢的答案,这样就可以以线性复杂度解决问题。
  • 首先,开两个数组 p r e 、 s u f pre、suf presuf
    1. p r e : pre: pre记录在 x x x 前面(不包括自己), 各个车厢颜色出现的次数
    2. s u f : suf: suf记录在 x x x 后面(不包括自己),各个车厢颜色出现的次数。
  • 那么我们就可以从左到右遍历各个车厢,每次利用树状数组维护的前缀和来得到答案
  • 假设当我们已知 x − 1 x-1 x1 位置的答案,那么 x x x 位置的答案,其实就是
    x − 1 x-1 x1 位置的答案
    + c o l x − 1 col_{x-1} colx1这个颜色和 [ x + 1 , n ] [x+1,n] [x+1,n]区间内的匹配数 ( s u f ) (suf) (suf)
    - c o l x col_{x} colx这个颜色和 [ 1 , x − 1 ] [1,x-1] [1,x1]这个区间内的匹配数 ( p r e ) (pre) (pre)
  • 考虑 x − 1 x-1 x1 x x x 的状态的话,还有不同颜色,在不同状态下的前后缀等。这样是很麻烦的,甚至为了控制不同位置不同状态两个变量,数组也需要扩大翻倍,导致难度骤增、MLE等尴尬局面。我们考虑直接通过循环来转移状态,也就是说,假如循环遍历是 i i i,那么所有的数据表达的都是 i i i位置的状态,省下了很多空间以及时间
  • 那么对于每一个车厢(左边车厢已经求解),
    1. 求解之前需要 s u f [ c o l x ] suf[col_x] suf[colx]--
      (因为记录的时候 s u f [ c o l x ] suf[col_x] suf[colx]是指 [ x , n ] [x,n] [x,n]这个区间内的 c o l x col_x colx颜色数量, x x x自己这个位置也是包含在内的)。
    2. 求解之后需要 p r e [ c o l x ] pre[col_x] pre[colx]++
      ( p r e [ c o l x ] pre[col_x] pre[colx] [ 1 , x − 1 ] [1,x-1] [1,x1]这个区间内 c o l x col_x colx的数量,并不包含自己,我们求解 x x x车厢之和,是要进入下一状态的,对于下一个车厢而言,当前车厢是可以匹配的)
  • 之前我们说,答案就是 a n s [ r x ] − a n s [ l x − 1 ] ans[r_x] - ans[l_x-1] ans[rx]ans[lx1],但是我们要注意,当前车厢为 x x x ,那么 x x x不可以和左边或者右边匹配的,所以需要先将前缀中减去和 c o l x col_x colx颜色相同的数量( x x x和前面匹配的数量就是 p r e [ c o l x ] pre[col_x] pre[colx]
  • 那么求得结果之和,需要向后迭代转移,也就是说,对于 x + 1 x+1 x+1位置而言, x x x 位置给的贡献,就是 s u f [ c o l x ] suf[col_x] suf[colx],加上即可。
  • 这样转移的部分就解决完了。
  • emmm其实个人觉得这道题还是很绕的,官方题解可能也不是很清楚(应该是我太弱了,理解不懂)。我感觉我这个分析的还可以,多读几遍慢慢思考还是可以懂的。

AC-Code

#include 
using namespace std;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define ll long long
const int maxn = 5e5 + 7;

#define lowbit(x) (x&(-x))

int n;
ll pre[maxn], suf[maxn], ans[maxn];
struct Node {
	int col, l, r;
}a[maxn];
void add(ll x, ll val) {
	while (x <= n) {
		ans[x] += val;
		x += lowbit(x);
	}
}
ll query(ll x) {
	ll res = 0;
	while (x) {
		res += ans[x];
		x -= lowbit(x);
	}
	return res;
}
int main() {
    ios;
	while (cin >> n) {
		for (int i = 1; i <= n; ++i) {
			cin >> a[i].col >> a[i].l >> a[i].r;
			++suf[a[i].col]; // 因为最开始是1位置的状态,他的suf,直接加起来就可以
		}
		for (int i = 1; i <= n; ++i) {
			--suf[a[i].col];
			add(a[i].col, -pre[a[i].col]);
			cout << query(a[i].r) - query(a[i].l - 1) << " ";
			++pre[a[i].col];
			add(a[i].col, suf[a[i].col]);
		}
	}
	return 0;
}

你可能感兴趣的:(2020牛客寒假集训营4,树状数组)