题目描述
牛牛是一名喜欢旅游的同学,在来到渡渡鸟王国时,坐上了颜色多样的火车。
牛牛同学在车上,车上有 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, lx≤coli=colj≤rx}中包含多少个元素。
换句话说,就是要求每一个车厢两边有多少对颜色相同的车厢,并且这一对车厢的颜色要在
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 1≤n≤5⋅105
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≤coli, li, ri≤5⋅105
题解
- 首先我们需要理解题意,有点绕。
- 比如样例:
(
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,x−1] 和右面车厢 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 ,这样的匹配数 - 知道题目要求什么了之后,我们思考如何去解决这个问题。由于题目比较奇特,先思考暴力的话如何解决这个问题:
- 肯定需要枚举 x ∈ [ 1 , n ] x\in[1,n] x∈[1,n],对于每一个 x x x ,我们可以在 a ∈ [ 1 , x − 1 ] a\in[1,x-1] a∈[1,x−1] 里找一个符合条件的 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++。
- 重复这个过程,就可以暴力求解了,但是复杂度爆炸。
- 现在我们来优化这一过程。
- 一般这样求解区间的情况,最容易想到的是线段树、树状数组、前缀和、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[lx−1]
- 下面考虑如何去维护这一数组
- 首先头尾肯定是0,因为有一端没有车厢了
- 我们尝试能否从上一个车厢的答案转移到当前车厢的答案,这样就可以以线性复杂度解决问题。
- 首先,开两个数组
p
r
e
、
s
u
f
pre、suf
pre、suf
- p r e : pre: pre:记录在 x x x 前面(不包括自己), 各个车厢颜色出现的次数
- s u f : suf: suf:记录在 x x x 后面(不包括自己),各个车厢颜色出现的次数。
- 那么我们就可以从左到右遍历各个车厢,每次利用树状数组维护的前缀和来得到答案
- 假设当我们已知
x
−
1
x-1
x−1 位置的答案,那么
x
x
x 位置的答案,其实就是
x − 1 x-1 x−1 位置的答案
+ c o l x − 1 col_{x-1} colx−1这个颜色和 [ 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,x−1]这个区间内的匹配数 ( p r e ) (pre) (pre)。 - 考虑 x − 1 x-1 x−1 和 x x x 的状态的话,还有不同颜色,在不同状态下的前后缀等。这样是很麻烦的,甚至为了控制不同位置不同状态两个变量,数组也需要扩大翻倍,导致难度骤增、MLE等尴尬局面。我们考虑直接通过循环来转移状态,也就是说,假如循环遍历是 i i i,那么所有的数据表达的都是 i i i位置的状态,省下了很多空间以及时间
- 那么对于每一个车厢(左边车厢已经求解),
- 求解之前需要
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自己这个位置也是包含在内的)。 - 求解之后需要
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,x−1]这个区间内 c o l x col_x colx的数量,并不包含自己,我们求解 x x x车厢之和,是要进入下一状态的,对于下一个车厢而言,当前车厢是可以匹配的)
- 求解之前需要
s
u
f
[
c
o
l
x
]
suf[col_x]
suf[colx]--
- 之前我们说,答案就是 a n s [ r x ] − a n s [ l x − 1 ] ans[r_x] - ans[l_x-1] ans[rx]−ans[lx−1],但是我们要注意,当前车厢为 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 <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;
}