题目描述
牛牛是一名喜欢旅游的同学,在来到渡渡鸟王国时,坐上了颜色多样的火车。
牛牛同学在车上,车上有 n 个车厢,每一个车厢有一种颜色。
他想知道对于每一个正整数
,集合
中包含多少个元素。
换句话说,就是要求每一个车厢两边有多少对颜色相同的车厢,并且这一对车厢的颜色要在
到
之间。其中
代表 i 号车厢的颜色,
,
代表颜色的限制。
输入描述:
第一行一个正整数n。
第二行 n 个三元组,每个三元组包括三个正整数 (
),输入中没有括号,这 3n 个正整数之间均只用空格隔开,详见样例。
输出描述:
输出一行 n 个非负整数代表答案。
输入
5
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
输出
0 3 4 3 0
备注:
题解
- 首先我们需要理解题意,有点绕。
- 比如样例:
这五个车厢,每个车厢内:中间是 ,左右分别是 。我们想找的是对于 车厢,找到左面车厢 和右面车厢 中 ,这样的匹配数 - 知道题目要求什么了之后,我们思考如何去解决这个问题。由于题目比较奇特,先思考暴力的话如何解决这个问题:
- 肯定需要枚举 ,对于每一个 ,我们可以在 里找一个符合条件的 ,然后从 里找颜色相同且符合条件的 ,然后 ++。
- 重复这个过程,就可以暴力求解了,但是复杂度爆炸。
- 现在我们来优化这一过程。
- 一般这样求解区间的情况,最容易想到的是线段树、树状数组、前缀和、dp等
- 首先感觉和
这种操作比较接近(
才不是因为题解这么做的) - 用树状数组维护 位置的 前缀和,换句话说就是对于 ,代表的是从 这个区间里,符合题目要求的颜色对数
- 那么对于每一个 答案就是
- 下面考虑如何去维护这一数组
- 首先头尾肯定是0,因为有一端没有车厢了
- 我们尝试能否从上一个车厢的答案转移到当前车厢的答案,这样就可以以线性复杂度解决问题。
- 首先,开两个数组
- 记录在 前面(不包括自己), 各个车厢颜色出现的次数
- 记录在 后面(不包括自己),各个车厢颜色出现的次数。
- 那么我们就可以从左到右遍历各个车厢,每次利用树状数组维护的前缀和来得到答案
- 假设当我们已知
位置的答案,那么
位置的答案,其实就是
位置的答案
+ 这个颜色和 区间内的匹配数
- 这个颜色和 这个区间内的匹配数 。 - 考虑 和 的状态的话,还有不同颜色,在不同状态下的前后缀等。这样是很麻烦的,甚至为了控制不同位置不同状态两个变量,数组也需要扩大翻倍,导致难度骤增、MLE等尴尬局面。我们考虑直接通过循环来转移状态,也就是说,假如循环遍历是 ,那么所有的数据表达的都是 位置的状态,省下了很多空间以及时间
- 那么对于每一个车厢(左边车厢已经求解),
- 求解之前需要
--
(因为记录的时候 是指 这个区间内的 颜色数量, 自己这个位置也是包含在内的)。 - 求解之后需要
++
( 指 这个区间内 的数量,并不包含自己,我们求解 车厢之和,是要进入下一状态的,对于下一个车厢而言,当前车厢是可以匹配的)
- 求解之前需要
--
- 之前我们说,答案就是 ,但是我们要注意,当前车厢为 ,那么 是不可以和左边或者右边匹配的,所以需要先将前缀中减去和 颜色相同的数量( 和前面匹配的数量就是 )
- 那么求得结果之和,需要向后迭代转移,也就是说,对于 位置而言, 位置给的贡献,就是 ,加上即可。
- 这样转移的部分就解决完了。
- 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;
}