bzoj 2906【颜色】

  这道题起初看错题意,询问的是在区间L,R内在l,r内的每个数值出现次数的平方的和,没注意到平方,就想到了一个带前缀和的思路。假如原本分T块,其实我们只需要预处理T个前缀就好了,每一个前缀,单独处理一个数组,再保存一个关于权值出现次数的前缀,每次查询L,R,进行分块,一个连续的块的答案可以通过前缀求得,两边多余的只需要扫一遍就好了。还写了个代码哈哈。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e4+10;
const int M=2e4+10;
int t,s,n,m,Q,p,q,pos,lan,a[N],sum[350][M];
int ask(int l,int r,int L,int R){
    //l=l^lan,r=r^lan,L=L^lan,R=R^lan;
    if(l>r) swap(l,r);
    if(L>R) swap(L,R);
    p=l/s,q=r/s;
    if(p%s==1) p++;
    else if(p%s) p+=2;
    else p++;
    if(q-p<0){
        int ans=0;
        for(int i=l;i<=r;++i) if(a[i]>=L&&a[i]<=R) ans++;
        return ans;
    }else{
        int ans=0;
        ans=sum[q][R]-sum[q][L-1]-sum[p-1][R]+sum[p-1][L-1];
        for(int i=l;i<=(p-1)*s;++i) if(a[i]>=L&&a[i]<=R) ans++;
        for(int i=q*s+1;i<=r;++i) if(a[i]>=L&&a[i]<=R) ans++;
        return ans;
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    t=sqrt(1.0*Q*n/m);s=n/t;
    for(int i=1;i<=t;++i){
        for(int j=1;j<=s*i;++j) sum[i][a[j]]++;
    }
    for(int i=1;i<=t;++i){
        for(int j=1;j<=m;++j) sum[i][j]+=sum[i][j-1];
    }
    for(int i=1;i<=Q;++i){
        int l,r,L,R;scanf("%d%d%d%d",&l,&r,&L,&R);
        printf("%d\n",lan=ask(l,r,L,R));
    }
    return 0;
}

  然而真实题意是还需要平方,所以不能这么写。那么可以有这么一种写法,我们像蒲公英那道题一样,对于每个块来说,还是存储在这个块内每个数出现的次数,并且存一个块的答案。两边多余的扫一遍更新答案就行了。关键在于怎么更新答案,其实一个平方把它拆开不就好了,假设当前某个值出现次数为x,你已经求了它的平方,这时候又出现了一次,你只需要加上1+2x就行了。复杂度和蒲公英是一样的,那么此题就做完了。

猜你喜欢

转载自www.cnblogs.com/kgxw0430/p/10269988.html