BZOJ 5016: [Snoi2017]一个简单的询问

Description

给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问读入l1,r1,l2,r2,需输出 i=1get(l1,r1,x)get(l2,r2,x) ∑ i = 1 ∞ g e t ( l 1 , r 1 , x ) g e t ( l 2 , r 2 , x )
get(l,r,x)表示计算区间[l,r]中,数字x出现了多少次。
Input

第一行,一个数字N,表示序列长度。
第二行,N个数字,表示a1~aN
第三行,一个数字Q,表示询问个数。
第4~Q+3行,每行四个数字l1,r1,l2,r2,表示询问。
N,Q≤50000
N1≤ai≤N
1≤l1≤r1≤N
1≤l2≤r2≤N
注意:答案有可能超过int的最大值
Output

对于每组询问,输出一行一个数字,表示答案
Sample Input

5

1 1 1 1 1

2

1 2 3 4

1 1 4 4
Sample Output

4

1

分析

我们要求的实际上就是区间[l1,r1]中的每个数在区间[l2,r2]中出现次数的和。
考虑按位置分块,预处理f[i,j]表示第i块中的每个数在[1,j]中出现次数的和。
询问的时候,整块的可以O(1)查询,那么多出来的怎么办呢?
如果拿可持久化线段树来查询的话,复杂度要多一个 logn l o g n
注意到现在的瓶颈在于如何快速求[l2,r2]中某个数的出现次数,那么只要把询问按照[l2,r2]莫队一下就好了。
时间复杂度 O(nn) O ( n n )

代码

#include <bits/stdc++.h>

const int N=50005;
const int M=250;

typedef long long ll;

int n,tot,bel[N],B,sta[M],end[M],t[N],a[N];
ll f[M][N],ans[N];

struct Data
{
    int l1,r1,l2,r2,id;
}q[N];

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

void preWork()
{
    B = sqrt(n);
    for (int i = 1; i <= n; i++)
    {
        bel[i] = (i + B - 1) / B;
        if (!sta[bel[i]]) 
            sta[bel[i]] = i;
        end[bel[i]] = i;
    }
    for (int i = 1; i <= bel[n]; i++)
    {
        for (int j = sta[i]; j <= end[i]; j++) 
            t[a[j]]++;
        for (int j = 1; j <= n; j++) 
            f[i][j] = f[i][j - 1] + t[a[j]];
        for (int j = sta[i]; j <= end[i]; j++) 
            t[a[j]] = 0;
    }
}

bool cmp(Data a,Data b)
{
    return bel[a.l2] < bel[b.l2] || bel[a.l2] == bel[b.l2] && a.r2 < b.r2;
}

ll query(int l1,int r1,int l2,int r2)
{
    ll ans = 0;
    if (bel[l1] == bel[r1])
    {
        for (int i = l1; i <= r1; i++) 
            ans += t[a[i]];
        return ans;
    }
    for (int i = l1; i <= end[bel[l1]]; i++) 
        ans += t[a[i]];
    for (int i = sta[bel[r1]]; i <= r1; i++) 
        ans += t[a[i]];
    for (int i = bel[l1] + 1; i < bel[r1]; i++)
        ans += f[i][r2] - f[i][l2 - 1];
    return ans;
}

void solve()
{
    for (int l = 1, r = 0, i = 1; i <= tot; i++)
    {
        for (; r < q[i].r2; r++) 
            t[a[r+1]]++;
        for (; l > q[i].l2; l--) 
            t[a[l-1]]++;
        for (; r > q[i].r2; r--) 
            t[a[r]]--;
        for (; l < q[i].l2; l++) 
            t[a[l]]--;
        ans[q[i].id] = query(q[i].l1,q[i].r1,q[i].l2,q[i].r2);
    }
}

int main()
{
    n = read();
    for (int i = 1; i <= n; i++) 
        a[i] = read();
    preWork();
    tot = read();
    for (int i = 1; i <= tot; i++) 
        q[i].l1 = read(), q[i].r1 = read(), q[i].l2 = read(), q[i].r2 = read(), q[i].id = i;
    std::sort(q + 1, q + tot + 1, cmp);
    solve(); 
    for (int i = 1; i <= tot; i++) 
        printf("%d\n",ans[i]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值