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\n",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/LLTYYC/p/9785521.html

时间: 2024-08-05 18:29:32

P4135 作诗的相关文章

P4135 作诗——分块

题目:https://www.luogu.org/problemnew/show/P4135 分块大法: 块之间记录答案,每一块记录次数前缀和: 注意每次把桶中需要用到位置赋值就好了: 为什么加了特判会 T 一个点? 代码如下: #include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int const maxn=1e5+5; int

【分块】P4135 作诗

分块太暴力惹... 没做出来.看了题解qaq 分析: 两头$\sqrt{n}$暴力维护 预处理ans[i][j],sum[i][j] sum[i][j]是一个前缀和,前i块值为j的数量 ans[i][j]表示第i块到第j块的答案总和 询问的时候先做两头,最后把ans[][]加上去就好了! 主要难点在预处理和卡常 1 #pragma GCC optimize(3) 2 #pragma GCC optimize(2) 3 #pragma GCC optimize("Ofast") 4 #i

【BZOJ2821】作诗(Poetize) 分块

Description 神犇SJY虐完HEOI之后给傻×LYD出了一题:SHY是T国的公主,平时的一大爱好是作诗.由于时间紧迫,SHY作完诗之后还要虐OI,于是SHY找来一篇长度为N的文章,阅读M次,每次只阅读其中连续的一段[l,r],从这一段中选出一些汉字构成诗.因为SHY喜欢对偶,所以SHY规定最后选出的每个汉字都必须在[l,r]里出现了正偶数次.而且SHY认为选出的汉字的种类数(两个一样的汉字称为同一种)越多越好(为了拿到更多的素材!).于是SHY请LYD安排选法.LYD这种傻×当然不会了

BZOJ 2821: 作诗(Poetize)( 分块 )

分块,分成N^0.5块.O(N^1.5)预处理出sm[i][j]表示前i块中j的出现次数, ans[i][j]表示第i~j块的答案. 然后就可以O(N^0.5)回答询问了.总复杂度O((N+Q)N^0.5) ----------------------------------------------------------------------------------------- #include<cstdio> #include<algorithm> #include<

刷题总结——分块算法(bzoj2821作诗)

题目: Description 神犇SJY虐完HEOI之后给傻×LYD出了一题:SHY是T国的公主,平时的一大爱好是作诗.由于时间紧迫,SHY作完诗 之后还要虐OI,于是SHY找来一篇长度为N的文章,阅读M次,每次只阅读其中连续的一段[l,r],从这一段中选出一 些汉字构成诗.因为SHY喜欢对偶,所以SHY规定最后选出的每个汉字都必须在[l,r]里出现了正偶数次.而且SHY认 为选出的汉字的种类数(两个一样的汉字称为同一种)越多越好(为了拿到更多的素材!).于是SHY请LYD安排选 法.LYD这

巴蜀4384 -- 【模拟试题】作诗(Poetize)

Description 神犇SJY虐完HEOI之后给傻×LYD出了一题:SHY是T国的公主,平时的一大爱好是作诗.由于时间紧迫,SHY作完诗之后还要虐OI,于是SHY找来一篇长度为N的文章,阅读M次,每次只阅读其中连续的一段[l,r],从这一段中选出一些汉字构成诗.因为SHY喜欢对偶,所以SHY规定最后选出的每个汉字都必须在[l,r]里出现了正偶数次.而且SHY认为选出的汉字的种类数(两个一样的汉字称为同一种)越多越好(为了拿到更多的素材!).于是SHY请LYD安排选法.LYD这种傻×当然不会了

做10年Windows程序员与做10年Linux程序员的区别(附无数评论)(开源软件相当于熟读唐诗三百首,不会作诗也会吟)

如果一个程序员从来没有在linux,unix下开发过程序,一直在windows下面开发程序, 同样是工作10年, 大部分情况下与在linux,unix下面开发10年的程序员水平会差别很大.我写这篇文章并不是想贬低windows下面开发的人,做windows开发的人看了可能会感觉不舒服,我并不是这个意思,我只是说说我自己的感受,我最早开始学习编程也是在windows下面的, 学的是VB,后来转到VC++,当时用的是VC6.0, 做windows下面的开发5年后转入linux下面做开发的,开始在li

BZOJ2821作诗【分块】

Description 神犇SJY虐完HEOI之后给傻×LYD出了一题:SHY是T国的公主,平时的一大爱好是作诗.由于时间紧迫,SHY作完诗之后还要虐OI,于是SHY找来一篇长度为N的文章,阅读M次,每次只阅读其中连续的一段[l,r],从这一段中选出一些汉字构成诗.因为SHY喜欢对偶,所以SHY规定最后选出的每个汉字都必须在[l,r]里出现了正偶数次.而且SHY认为选出的汉字的种类数(两个一样的汉字称为同一种)越多越好(为了拿到更多的素材!).于是SHY请LYD安排选法.LYD这种傻×当然不会了

胡乔木替毛泽东撰文作诗???

中央文献研究室斥胡乔木替毛泽东撰文作诗谣言 我来说两句 毛主席气吞山河无人能比,永远怀念毛主席! 回复顶一下 搜狐社区: wchpjxs2011-05-26 08:58:04153400848 毛主席的写诗词的境界不是任何人都能达到的,从这点就可以看出,那些别有用心的人愚昧啊 回复顶一下 搜狐社区: liyu_ldz2011-05-26 09:04:17153401213 官方的表态终于让反毛主席的跳梁小丑们无地自容了! 回复顶一下 搜狐社区: 春如酒.2011-05-26 08:59:4115