bzoj5016: [Snoi2017]一个简单的询问

题意

自己看吧,不写了

题解

这题还想了我挺久。。
考虑分块。
f[i][j]表示—–>i是块,j是位置
l1=L[i],r1=R[i];
l2=1,r2=j的答案
这个是可以扫一次就预处理出来。。
然后就可以弄了。。
在块里面的就这么弄,多余的就用一个线段树来查询某一段区间有多少个值为x的数就好

具体看代码吧,有少量注释,应该看得懂

#include<cstdio>
#include<cmath>
#include<cstring>
typedef long long LL;
const int N=50005;
const int NN=225;
int n,nn;
int a[N];
int son[N*30][2];
LL d[N*30];
int root[N];
int num;
void add (int &now,int l,int r,int x)
{
    if (now==0) now=++num;
    d[now]++;
    if (l==r) return ;
    int mid=(l+r)>>1;
    if (x<=mid) add(son[now][0],l,mid,x);
    else add(son[now][1],mid+1,r,x);
}
int cnt,L[N],R[N],belong[N];  
void prepare()  
{   
    L[1]=1;belong[1]=1;cnt=1;  
    for (int u=1;u<=n;u++)  
    {  
        belong[u]=cnt;  
        if (u%nn==0)  
        {  
            R[cnt]=u;  
            cnt++;  
            L[cnt]=u+1;  
        }  
    }  
    cnt=belong[n];R[cnt]=n;
}  
LL f[NN][N];//第i块和1~i的答案
LL X[N];//这个数字有多少个,预处理用
void init ()
{
    cnt=num=0;
    scanf("%d",&n);nn=sqrt(n);
    for (int u=1;u<=n;u++) 
    {
        scanf("%d",&a[u]);
        add(root[a[u]],1,n,u);
    }
}
void prepare1 ()//预处理f数组 
{
    memset(X,0,sizeof(X));
    for (int u=1;u<=cnt;u++)//枚举每一块
    {
        for (int i=L[u];i<=R[u];i++)
            X[a[i]]++;
        for (int i=1;i<=n;i++)
            f[u][i]=f[u][i-1]+X[a[i]];
        for (int i=L[u];i<=R[u];i++)
            X[a[i]]--;
    }
}
LL get (int now,int l,int r,int L,int R)
{
    if (now==0) return 0;
    if (l==L&&r==R) return d[now];
    int mid=(l+r)>>1;
    if (R<=mid) return get(son[now][0],l,mid,L,R);
    else if (L>mid) return get(son[now][1],mid+1,r,L,R);
    else return get(son[now][0],l,mid,L,mid)+get(son[now][1],mid+1,r,mid+1,R);
}
void solve ()
{
    int Q;
    scanf("%d",&Q);
    while (Q--)
    {
        int l,r,l1,r1;
        scanf("%d%d%d%d",&l,&r,&l1,&r1);
        //printf("YES%d %d\n",belong[l],belong[r]);
        LL ans=0;
        if (belong[l]==belong[r])//在同一块以内
        {
            for (int u=l;u<=r;u++)
                ans=ans+get(root[a[u]],1,n,l1,r1);
        }
        else
        {
            for (int u=belong[l]+1;u<belong[r];u++)
                ans=ans+f[u][r1]-f[u][l1-1];
            for (int u=l;u<=R[belong[l]];u++)
                ans=ans+get(root[a[u]],1,n,l1,r1);
            for (int u=L[belong[r]];u<=r;u++)
                ans=ans+get(root[a[u]],1,n,l1,r1);
        }
        printf("%lld\n",ans);
    }
}
int main()
{
    init();
    prepare();
    prepare1();
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值