[Codeforces 877F] Ann and Books

题目链接:http://codeforces.com/problemset/problem/877/F

题目大意:有n本书排成一个序列, 每本书属于1类或2类, 第i本书上有 ai 道题。 给定一个k, 一段可购买的子区间[l,r]需要满足1类书的题目总数刚好比2类书的题目总数多k道。有q组询问,每次询问[l,r]中有多少个子区间满足该条件。 (n105,109k109,0ai109,q105)

思路:让1类书a[i] = a[i],2类书a[i] = -a[i],求一个前缀和sum后, 对于一个sum[i], 只需要知道合法的sum[i] - k的值出现了几次。

考虑如果已知了[l, r]的答案, 通过维护当前每个前缀和的值出现的次数, 我们可以在O(1)的时间内求出[l - 1, r]或[l + 1, r]或[l, r - 1]或[l, r + 1]的答案, 实现答案区间的转移。 可以用莫队算法在 O(qn) 解决所有的询问。

也就是将询问[l, r]按照以 ln 为第一关键字, r为第二关键字, 排序后依次处理即可。

PS:通过离散化和预处理每个sum[i], sum[i] + k, sum[i] - k对应的哈希值后, 可以让询问的主题过程中不带log。

#include <map>
#include <queue>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>

#define ll long long

#define mid ((l + r) >> 1)

using namespace std;

const int N = (int)2e5 + 10;
const int M = 300;

int n, k, q, a[N], t[N]; ll sum[N];
int tot, cnt[N * 4], x[N], pre[N], suf[N]; ll c[N * 4], output[N];
struct Query{
    int l, r, id;
    bool operator<(const Query &_)  const{
        if (l / M != _.l / M) return l / M < _.l / M;
        return r < _.r;
    } 
}que[N];

int main(){
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; i ++) scanf("%d", t + i);
    for (int i = 1; i <= n; i ++) scanf("%d", a + i);

    c[++ tot] = 0; c[++ tot] = k; c[++ tot] = -k;

    for (int i = 1; i <= n; i ++){
        sum[i] = sum[i - 1] + (t[i] == 1 ? a[i] : -a[i]);
        c[++ tot] = sum[i]; c[++ tot] = sum[i] - k; c[++ tot] = sum[i] + k;
    }

    sort(c + 1, c + tot + 1);
    tot = unique(c + 1, c + tot + 1) - c - 1;

    for (int i = 0; i <= n; i ++){
        x[i] = lower_bound(c + 1, c + tot + 1, sum[i]) - c;
        pre[i] = lower_bound(c + 1, c + tot + 1, sum[i] - k) - c;
        suf[i] = lower_bound(c + 1, c + tot + 1, sum[i] + k) - c;
    }

    scanf("%d", &q);
    for (int i = 1; i <= n; i ++) scanf("%d %d", &que[i].l, &que[i].r), que[i].id = i;
    sort(que + 1, que + q + 1);

    int L = 0, R = 1; ll ans = k == sum[1];
    cnt[x[0]] ++; cnt[x[1]] ++;

    for (int i = 1; i <= q; i ++){
        int l = que[i].l - 1, r = que[i].r, id = que[i].id;
        while (l < L){
            ans += cnt[suf[L - 1]]; cnt[x[L - 1]] ++; L --;
        }
        while (l > L){
            cnt[x[L]] --; ans -= cnt[suf[L]]; L ++;
        }
        while (r < R){
            cnt[x[R]] --; ans -= cnt[pre[R]]; R --;
        }
        while (r > R){
            ans += cnt[pre[R + 1]]; cnt[x[R + 1]] ++; R ++;
        }

        output[id] = ans;
    }

    for (int i = 1; i <= q; i ++) printf("%lld\n", output[i]);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值