ZOJ-4117 BaoBao Loves Reading(第十届山东省赛E题)
ZOJ-4117 VJ传送门
考点:树状数组(+容斥)
题意
现在有一个书架和一个书桌,书架上有 n 本书,书桌有容量,不能放超过容量的书,现在需要按照顺序读书,给出一个读书的顺序,要求给出书桌容量对应 1-n 的情况下,最少要拿多少次书。
题解
首先按照题面描述的做法肯定是会超时的。
这道题目我们可以来求对应每个区间里面有多少个不同的数字。但是这个信息其实并不好获取,暴力获取也是 n 2 n^2 n2 的时间复杂度。最关键的是这样记录不符合前缀和的性质。
所以我们需要换一种思路,我们维护一个数组 sum[] ,sum[x] 表示在区间 [lowbit(x),x] 中,有多少个只有我这个区间有而向后更大的区间没有的数字,所以 query(x) 可以表示在区间 [1,x] 中有多少个只有我这个区间有而向后更大的区间没有的数字,此时我们如果需要 [l,r] 区间内有多少个不同的数字,就可以通过类似前缀和的方式求出,diff_cnt[l][r] = query(r) - query(l-1)
。这里 sum[] 的维护就可以用树状数组来做。具体 sum[] 和对应的 query(i) 内部的值可见下面的图。
拿 sum[2] 来说,因为区间 [1,2] 的数字分别为 4 和 3 ,在后面的区间中都有,所以 sum[2] = 0 。
sum[4] = 1 ,是因为
a
4
=
2
a_4=2
a4=2 ,在后续区间中没有出现过 2 。
sum[5] = 1,是因为在树状数组意义下,覆盖区间为 a[5] ,详情可以参见树状数组的图。
至于为什么 [l,r] 区间内有多少个不同的数字可以通过 query(r) - query(l-1)
来求出只需要简单的容斥思想就可以明白。
然后对于连续相同的数字 a i a_i ai a j a_j aj 之间有多少个不同的数字,记为 k ,桌子的容量如果大于 k ,那么这本书没有被放回,仍在桌上,所以不需要去拿,代码中用 dp[len] 来表示在桌子容量为 len 时,有多少本书不需要被拿。一开始处理完的 dp[len] 仅仅为两个相同数字之间恰好为 len 个不同的数时,当两个数字之间不同数字的个数比 len 小时,也不需要拿,所以从小到大更新。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int T, n;
int sum[maxn];
int last[maxn], dp[maxn];
int lowbit(int x) { return x & -x; }
void add(int x, int cnt) {
while (x <= n) {
sum[x] += cnt;
x += lowbit(x);
}
}
int query(int x) {
int ans = 0;
while (x > 0) {
ans += sum[x];
x -= lowbit(x);
}
return ans;
}
inline void init() { for (int i = 0; i <= n; i++) sum[i] = last[i] = dp[i] = 0; }
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
init();
for (int i = 1, x; i <= n; i++) {
scanf("%d", &x);
if (last[x] > 0) {
int len = query(i) - query(last[x]);
add(i, 1);
add(last[x], -1);
dp[len]++;
}
else add(i, 1);
last[x] = i;
}
// puts("this is sum[]");
// for (int i = 1; i <= n; i++) printf("%d ", sum[i]); printf("\n");
for (int i = 1; i <= n; i++) {
dp[i] += dp[i-1];
printf("%d", n-dp[i-1]);
if (i != n) printf(" ");
else printf("\n");
}
}
return 0;
}