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

题目传送门


题目描述

牛牛是一名喜欢旅游的同学,在来到渡渡鸟王国时,坐上了颜色多样的火车。
牛牛同学在车上,车上有 n 个车厢,每一个车厢有一种颜色。
他想知道对于每一个正整数 x [ 1 ,   n ] x \in [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 \} 中包含多少个元素。
换句话说,就是要求每一个车厢两边有多少对颜色相同的车厢,并且这一对车厢的颜色要在 l x l_x r x r_x 之间。其中 c o l i col_i 代表 i 号车厢的颜色, l x l_x , r x r_x 代表颜色的限制。


输入描述:

第一行一个正整数n。
第二行 n 个三元组,每个三元组包括三个正整数 ( c o l i , l i , r i col_i, l_i, r_i ),输入中没有括号,这 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

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


题解

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

AC-Code

#include <bits/stdc++.h>
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;
}
发布了178 篇原创文章 · 获赞 109 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/104275449