把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3396】哈希冲突(大力分块)

点此看题面

大致题意: 给你一个长度为\(n\)的数组\(val\)以及\(m\)个操作,操作有两种:一种是将\(val_x\)修改为\(y\),另一种操作是求出\(\sum val_i(i\%x=y)\)

朴素的暴力

我们先来写一个朴素的暴力,代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
#define N 150000
int FoutSize=0,OutputTop=0;char Fin[Fsize],*FinNow=Fin,*FinEnd=Fin,Fout[Fsize],OutputStack[Fsize];
using namespace std;
int n,Q,a[N+5];
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline void read_alpha(char &x)
{
    while(!isalpha(x=tc()));
} 
inline void write(int x)
{
    if(!x) return (void)pc('0');
    while(x) OutputStack[++OutputTop]=x%10+48,x/=10;
    while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;
}
int main()
{
    register int i,x,y,ans;register char op;
    for(read(n),read(Q),i=1;i<=n;++i) read(a[i]);
    while(Q--)
    {
        read_alpha(op),read(x),read(y);
        if(op^'A') a[x]=y;//对于修改操作直接O(1)修改
        else
        {
            for(ans=0,i=y;i<=n;i+=x) ans+=a[i];//据题意统计答案
            write(ans),pc('\n');//输出
        }
    }
    return fwrite(Fout,1,FoutSize,stdout),0;
}

我们可以惊喜地发现,这样就有\(91\)分了。如果你写得足够优秀,说不定可以直接AC。

预处理答案+暴力更新

我们可以用\(ans_{i,j}\)来记录询问\(i,j\)时的答案。

首先,\(O(n^2)\)预处理出答案,并用\(O(n^2)\)大小的数组把答案存下来,对于询问,可以\(O(1)\)输出答案,并\(O(n)\)暴力更新。

然后,我们悲剧地发现\(n≤150000\),还没等到\(TLE\),直接内存炸飞了。。。

代码略。

如何优化

我们可以对第二个上天了的代码进行优化。

记录全部答案毕竟所耗内存太大了,能不能只记录一部分的答案,然后另一部分暴力算呢?

就可以考虑分块

我们可以先\(O(n\sqrt n)\)暴力预处理模数\(≤\sqrt n\)时的答案,并用\(ans\)数组将其存储下来。

然后,对于修改,我们可以在\(O(\sqrt n)\)的时间复杂度内枚举每一个小于等于\(\sqrt n\)的模数,然后更新对应的\(ans\)

对于询问,我们分两种情况讨论:

  • 如果模数\(≤O(\sqrt n)\),就直接输出\(ans\)
  • 如果模数\(>O(\sqrt n)\),就暴力求解,且枚举的数的个数肯定小于\(O(\sqrt n)\)

这样的算法应该是\(O((n+m)\sqrt n)\)的。

一个玄学的小优化

虽然我个人认为最优块的大小应该是\(O(\sqrt n)\),但是,实践证明,以\(O(\sqrt[3] n)\)为块的大小要比\(O(\sqrt n)\)快很多。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
#define N 150000
#define SIZE 400 
int FoutSize=0,OutputTop=0;char Fin[Fsize],*FinNow=Fin,*FinEnd=Fin,Fout[Fsize],OutputStack[Fsize];
using namespace std;
int n,Q,blo,a[N+5],ans[SIZE][SIZE];
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline void read_alpha(char &x)
{
    while(!isalpha(x=tc()));
} 
inline void write(int x)
{
    if(!x) return (void)pc('0');
    while(x) OutputStack[++OutputTop]=x%10+48,x/=10;
    while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;
}
int main()
{
    register int i,j,x,y,res;register char op;
    for(read(n),read(Q),blo=pow(n,0.33333),i=1;i<=n;++i) read(a[i]);//读入数据,这里的块的大小取n^(1/3)要比n^(1/2)快很多
    for(i=1;i<=n;++i) for(j=1;j<=blo;++j) ans[j][i%j]+=a[i];//O(n^(4/3))暴力预处理出模数≤n^(1/3)时的答案
    while(Q--)
    {
        read_alpha(op),read(x),read(y);
        if(op^'A')//对于修改操作
        {
            for(i=1;i<=blo;++i)//枚举每一个小于等于n^(1/3)的模数,修改对应答案
                ans[i][x%i]+=y-a[x];//减去原来的值,加上更新之后的值
            a[x]=y;//修改
        }
        else//对于询问操作
        {
            if(x<=blo) {write(ans[x][y]),pc('\n');continue;}//如果模数≤n^(1/3),直接输出预处理得到的答案
            for(i=y,res=0;i<=n;i+=x) res+=a[i];//否则,暴力统计答案
            write(res),pc('\n');//输出答案
        }
    }
    return fwrite(Fout,1,FoutSize,stdout),0;
}
posted @ 2018-10-28 21:26  TheLostWeak  阅读(178)  评论(0编辑  收藏  举报