分块/莫队大法

BZOJ2821 作诗,区间[L,R]出现正偶数次的个数,题目要求必须在线。对于这种防莫队算法题,那也是相当无语。好吧,上分块。首先如何能高效查询一段区间内某个数字出现多少次,暴力就是扫一遍O(n),不那么暴力,我们可以将所有数字从小到大排序,那么同一个数显然是一段连续区间,我们扫一遍,记录最左和最右的那个数在原数组的下标,那么对于L和R分别二分,然后再减一下,不就知道个数了,利用这一点,我们块内就这么暴力,块之间需要预处理f[i][j]表示第i块到第j块出现正偶数次的数字个数,然后考虑端点两块对这中间块的影响,仍然利用上面二分方法,查询某个数组在[L,R]和中间块出现的次数,然后就可以考虑加减1的问题了。具体见代码。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define Maxn 100010
#define Maxm 1300
using namespace std;

inline int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
int n,m,c,qt,sz;
int pos[Maxn],a[Maxn];
int L[Maxm],R[Maxm];
void init(){
    sz=sqrt(n/log(n)*log(2));
    m=(n+sz-1)/sz;
    for(int i=0;i<n;i++) pos[i]=i/sz;
    for(int i=0;i<m;i++) L[i]=i*sz,R[i]=L[i]+sz-1;
    R[m-1]=n-1;
}
int f[Maxm][Maxm],cl[Maxn],p[Maxn];
int fst[Maxn],lst[Maxn];
bool cmp(int i,int j){
    return a[i]<a[j]||a[i]==a[j]&&i<j;
}
void pre(){
    for(int i=0;i<m;i++){ //预处理块i到块j
        int cnt=0;
        for(int j=L[i];j<n;j++){
            cl[a[j]]++;
            if(!(cl[a[j]]&1)) cnt++; //偶数
            else if(cl[a[j]]>2) cnt--; //奇数大于2
            f[i][pos[j]]=cnt;
        }
        for(int j=L[i];j<n;j++) cl[a[j]]=0;
    }
    for(int i=0;i<n;i++) p[i]=i;
    for(int i=1;i<=n;i++) fst[i]=-1;
    sort(p,p+n,cmp); //按照a[i]排序
    for(int i=0;i<n;i++){ //预处理每种颜色起始和终止位置
        if(fst[a[p[i]]]<0) fst[a[p[i]]]=i;
        lst[a[p[i]]]=i;
    }
}
int vis[Maxn];
int ck(int l,int r,int x){ //查询区间[l,r]颜色为x的数量
    int len=upper_bound(p+fst[x],p+lst[x]+1,r)-
    lower_bound(p+fst[x],p+lst[x]+1,l);
    return max(0,len);
}
void cal(int i,int l,int r,int &ans){
    if(vis[a[i]]) return;
    int c1=ck(l,r,a[i]),c2=ck(R[pos[l]]+1,L[pos[r]]-1,a[i]);
    vis[a[i]]=1;
    if((c2&1)||!c2){ //原先奇数或0
        if(!(c1&1)) ans++; //现在偶数
    }
    else{ //原先偶数且大于0
        if(c1&1) ans--; //现在奇数
    }
}
int query(int l,int r){
    int ans=0;
    if(pos[l]==pos[r]||pos[l]+1==pos[r]){
        for(int i=l;i<=r;i++){
            if(vis[a[i]]) continue;
            int cnt=ck(l,r,a[i]);
            if(!(cnt&1)&&cnt>1) ans++;
            vis[a[i]]=1;
        }
        for(int i=l;i<=r;i++) vis[a[i]]=0; //清0
    }
    else{
        ans=f[pos[l]+1][pos[r]-1]; //按块处理
        for(int i=l;i<=R[pos[l]];i++) cal(i,l,r,ans);
        for(int i=L[pos[r]];i<=r;i++) cal(i,l,r,ans);
        for(int i=l;i<=R[pos[l]];i++) vis[a[i]]=0;
        for(int i=L[pos[r]];i<=r;i++) vis[a[i]]=0;
    }
    return ans;
}
int main()
{
    int l,r;
    n=read(),c=read(),qt=read();
    init();
    for(int i=0;i<n;i++) a[i]=read();
    pre();
    int ans=0;
    for(int i=0;i<qt;i++){
        l=read(),r=read();
        l=(l+ans)%n,r=(r+ans)%n;
        if(l>r) swap(l,r);
        ans=query(l,r);
        printf("%d\n",ans);
    }
	return 0;
}


BZOJ3809 询问区间[L,R]内[a,b]区间中的数出现了多少次,终于可以离线了。上莫队大法,很容易想到利用树状数组来维护,但是这么做修改复杂度将是O(nsqrt(n)log(n)),不怎么好,于是我们知道每个数字都是[1,n],那么对数字分块吧,修改O(1),查询O(sqrt(n)),最后复杂度主要是询问O(msqrt(n)),稍好一点,当然块大小取sqrt(n/2),常数更小一点。

上代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define Maxn 100010
using namespace std;

inline int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
int pos[Maxn];
struct query{
    int l,r,a,b,id;
    void Read(){
        l=read(),r=read(),a=read(),b=read();
        l--,r--,a--,b--;
    }
    bool operator<(const query &c)const{
        return pos[l]<pos[c.l]||pos[l]==pos[c.l]&&pos[r]<pos[c.r];
    }
}q[Maxn*10];
int n,m,qt,sz;
void init(){
    sz=sqrt(n/2);
    m=(n+sz-1)/sz;
    for(int i=0;i<n;i++) pos[i]=i/sz;
}
int a[Maxn],c[Maxn],b[Maxn];
void add(int x){
    if(++c[a[x]]==1) b[pos[a[x]]]++;
}
void del(int x){
    if(--c[a[x]]==0) b[pos[a[x]]]--;
}
int query(int l,int r){
    int ans=0;
    if(pos[l]==pos[r]){
        for(int i=l;i<=r;i++) ans+=c[i]!=0;
    }
    else{
        for(int i=pos[l]+1;i<pos[r];i++) ans+=b[i];
        for(int i=l;i<(pos[l]+1)*sz;i++) ans+=c[i]!=0;
        for(int i=pos[r]*sz;i<=r;i++) ans+=c[i]!=0;
    }
    return ans;
}
int res[Maxn*10];
int main()
{
    n=read(),qt=read();
    init();
    for(int i=0;i<n;i++) {a[i]=read();a[i]--;}
    for(int i=0;i<qt;i++){
        q[i].Read();
        q[i].id=i;
    }
    sort(q,q+qt);
    int l=0,r=-1;
    for(int i=0;i<qt;i++){
        while(r<q[i].r) add(++r);
        while(r>q[i].r) del(r--);
        while(l>q[i].l) add(--l);
        while(l<q[i].l) del(l++);
        res[q[i].id]=query(q[i].a,q[i].b);
    }
    for(int i=0;i<qt;i++)
        printf("%d\n",res[i]);
    return 0;
}

BZOJ3289 区间逆序对,莫队+树状数组,非常简单...注意离散化

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define Maxn 50010
using namespace std;

inline int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
int pos[Maxn];
struct query{
    int l,r,id;
    void Read(){
        l=read(),r=read();
        l--,r--;
    }
    bool operator<(const query &c)const{
        return pos[l]<pos[c.l]||pos[l]==pos[c.l]&&pos[r]<pos[c.r];
    }
}q[Maxn];
int n,m,qt,sz;
void init(){
    sz=sqrt(n);
    m=(n+sz-1)/sz;
    for(int i=0;i<n;i++) pos[i]=i/sz;
}
int a[Maxn],b[Maxn],c[Maxn];
void add(int x,int y){
    for(int i=x;i<=n;i+=i&-i)
        c[i]+=y;
}
int sum(int x){
    int ans=0;
    for(int i=x;i;i-=i&-i)
        ans+=c[i];
    return ans;
}
int res[Maxn];
void query(){
    int ans=0;
    int l=0,r=-1;
    for(int i=0;i<qt;i++){
        while(r<q[i].r){
            r++;
            ans+=sum(n)-sum(a[r]);
            add(a[r],1);
        }
        while(r>q[i].r){
            ans-=sum(n)-sum(a[r]);
            add(a[r],-1);
            r--;
        }
        while(l>q[i].l){
            l--;
            ans+=sum(a[l]-1);
            add(a[l],1);
        }
        while(l<q[i].l){
            ans-=sum(a[l]-1);
            add(a[l],-1);
            l++;
        }
        res[q[i].id]=ans;
    }
}
int main()
{
    n=read();
    init();
    for(int i=0;i<n;i++) {a[i]=b[i]=read();}
    sort(b,b+n);
    for(int i=0;i<n;i++)
        a[i]=lower_bound(b,b+n,a[i])-b+1;
    qt=read();
    for(int i=0;i<qt;i++){
        q[i].Read();
        q[i].id=i;
    }
    sort(q,q+qt);
    query();
    for(int i=0;i<qt;i++)
        printf("%d\n",res[i]);
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值