1. 程式人生 > >【洛谷3396】雜湊衝突(大力分塊)

【洛谷3396】雜湊衝突(大力分塊)

點此看題面

大致題意:給你一個長度為n的陣列val以及m個操作,操作有兩種:一種是將valx修改為y,另一種操作是求出vali(i%x=y)

樸素的暴力

我們先來寫一個樸素的暴力,程式碼如下:

int main()
{
    register int i,Q,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。

預處理答案+暴力更新

我們可以用ansi,j來記錄詢問i,j時的答案。

首先,O(n2)預處理出答案,並用O(n2)大小的陣列把答案存下來,對於詢問,可以O(1)輸出答案,並O(n)暴力更新。

然後,我們悲劇地發現n150000,還沒等到TLE,直接記憶體炸飛了。。。

程式碼略。

如何優化

我們可以對第二個上天了的程式碼進行優化。

記錄全部答案畢竟所耗記憶體太大了,能不能只記錄一部分的答案,然後另一部分暴力算呢?

就可以考慮分塊

我們可以先O(nn)暴力預處理模數n時的答案,並用ans陣列將其儲存下來。

然後,對於修改,我們可以在O(n)的時間複雜度內列舉每一個小於等於

n的模數,然後更新對應的ans

對於詢問,我們分兩種情況討論:

  • 如果模數O(n),就直接輸出ans
  • 如果模數>O(n),就暴力求解,且列舉的數的個數肯定小於O(n)

這樣的演算法應該是O((n+m)n)的。

一個玄學的小優化

雖然我個人認為最優塊的大小應該是O(n),但是,實踐證明,以O(n3)為塊的大小要比O(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;
}