ZOJ-4117 BaoBao Loves Reading(第十届山东省赛E题)

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值