zoukankan      html  css  js  c++  java
  • P4135 作诗

    传送门

    分块

    设sum[ i ] [ j ] 存从左边到第 i 块时,数字 j 的出现次数

     f [ i ] [ j ] 存从第 i 块,到第 j 块的一整段的答案

    那么最后答案就是一段区间中几块整段的答案加上两边小段的贡献

    考虑两边小段的影响,对于每一个出现的数

    它可能会使答案增加(使原本大区间中出现奇数次的数变成出现偶数次)

    也可能使答案减少(使原本大区间中出现偶数次的数变成出现奇数次)

    有了 sum 数组我们可以很方便地求出大区间中每个数的出现次数

    对小段的每个数直接计算一下它对答案的贡献

    开一个 cnt[ i ] 记录一下之前每个数 i 出现的次数就好了

    //bl,br是大区间的左右边界的块
    ans=f[bl][br];//ans初值为大区间的答案
    for(int i=l;i<L[bl];i++)//L[i]存第i块的左端点
    {
        cnt[a[i]]++;//记录a[i]在小段出现的次数
        t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1/*注意是bl-1*/][a[i]];//计算此时a[i]出现的次数
        if(!(t&1)) ans++;//如果a[i]出现的次数变成了偶数次,答案就加一
        else if(t>1) ans--;//否则如果出现次数超过2次且使出现次数变成奇数次,答案减1
        //要特殊考虑t=1的情况,t=1时不会对答案有影响,因为t=0时不算出现偶数次
    }
    for(int i=L[br+1];i<=r;i++)//注意i的范围
    {
        cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]];
        if(!(t&1)) ans++;
        else if(t>1) ans--;
    }
    for(int i=l;i<L[bl];i++) cnt[a[i]]--; for(int i=L[br+1];i<=r;i++) cnt[a[i]]--;//别忘记还原cnt,以后询问还要用,注意不要用memset
    关于询问

    然后来考虑如何预处理

    sum数组很好求,关键是 f

    好像枚举每个块复杂度会爆炸...

    我们来看看处理询问时的方法,对每个数都计算贡献

    预处理就相当于询问每两个块之间的贡献

    我们可以用同样的方法,cnt[ i ] 记录 i 出现了几次

    从左到右扫,计算每个数对答案的贡献,如果扫到一个区间的右端点了,就记录一波 f

    //bel[i]表示点i属于第几个块
    for(int i=1;i<=bel[n];i++)//枚举每个左块
    {
        t=0;
        for(int j=L[i];j<=n;j++)//考虑右边所有块
        {
            cnt[a[j]]++;//记录
            if(!(cnt[a[j]]&1)) t++;
            else if(cnt[a[j]]>1) t--;
            //考虑对答案的贡献
            if(bel[j]!=bel[j+1]) f[i][bel[j]]=t;//如果到了一个区间的右端点,更新f
        }
        for(int j=L[i];j<=n;j++) cnt[a[j]]--;//别忘了还原cnt
    }
    关于预处理

    这样我们就可以在 O( n*sqrt(n) )的时间内预处理,在 O( sqrt(n)+2*sqrt(n) ) 的时间处理询问

    注意一下常数就轻松过了

    细节很多的一题

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    inline 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<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    const int N=1e5+7,M=327;
    int n,m,q,ans;
    int a[N],bel[N],L[M];
    int sum[M][N],f[M][M];
    
    int cnt[N];
    inline void pre()//预处理
    {
        int t=0;
        for(int i=1;i<=bel[n];i++)
            for(int j=1;j<=m;j++) sum[i][j]+=sum[i-1][j];
        for(int i=1;i<=bel[n];i++)
        {
            t=0;
            for(int j=L[i];j<=n;j++)
            {
                cnt[a[j]]++;
                if(!(cnt[a[j]]&1)) t++;
                else if(cnt[a[j]]>1) t--;
                if(bel[j]!=bel[j+1]) f[i][bel[j]]=t;
            }
            for(int j=L[i];j<=n;j++) cnt[a[j]]--;
        }
    }
    inline void query(int l,int r)//处理询问
    {
        ans=0; int t=0;
        if(bel[l]+1>=bel[r])//对没有大区间的情况特殊处理
        {
            for(int i=l;i<=r;i++)//直接爆力计算每个数的贡献
            {
                cnt[a[i]]++;
                if(!(cnt[a[i]]&1)) ans++;
                else if(cnt[a[i]]>1) ans--;
            }
            for(int i=l;i<=r;i++) cnt[a[i]]--;//清空cnt
            return;
        }
        int bl=bel[l-1]+1,br=bel[r+1]-1;//计算bl,br,细节
        //l-1是考虑当l在一个块最左边的时候,那l在的块整块都会每计算,直接整个拿出来计算就好了,r+1同理
        ans=f[bl][br];//初值
        for(int i=l;i<L[bl];i++)//对左边的小段计算贡献
        {
            cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]];
            if(!(t&1)) ans++;
            else if(t>1) ans--;
        }
        for(int i=L[br+1];i<=r;i++)//对右边的小段计算贡献
        {
            cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]];
            if(!(t&1)) ans++;
            else if(t>1) ans--;
        }
        for(int i=l;i<L[bl];i++) cnt[a[i]]--; for(int i=L[br+1];i<=r;i++) cnt[a[i]]--;//清空
        return;
    }
    int main()
    {
        n=read(); m=read(); q=read();
        int t=sqrt(n)+1;
        for(int i=1;i<=n;i++)
        {
            a[i]=read(); bel[i]=(i-1)/t+1;//处理bel
            if(bel[i]!=bel[i-1]) L[bel[i]]=i;//处理L
            sum[bel[i]][a[i]]++;//此时sum只包括第i块的数量
        }
        bel[n+1]=bel[n]+1; L[bel[n+1]]=n+1;//重要的细节,有时我的代码考虑边界时会访问到n+1的点
    
        pre();
    
        int l,r;
        for(int i=1;i<=q;i++)
        {
            l=read(); r=read();
            l=(l+ans)%n+1; r=(r+ans)%n+1;
            if(l>r) swap(l,r);//处理l,r
            query(l,r);
            printf("%d
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    如来神掌第一式第十九招----Samba 详解
    如来神掌第一式第十八招----PXE 详解
    如来神掌第二式第七招----典型shell示例
    如来神掌第二式第六招----Shell游戏案例之猜数字
    如来神掌第二式第五招----Shell游戏案例之贪吃蛇
    如来神掌第二式第四招----Shell应用案例之网络监控
    如来神掌第二式第三招----Shell应用案例之主机监控
    如来神掌第二式第二招----Shell应用案例之大文件删除
    如来神掌第二式第一招----Shell脚本基础
    如来神掌第一式第十七招----Mysql 详解
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9785521.html
Copyright © 2011-2022 走看看