1. 程式人生 > >[luogu3396] 雜湊衝突

[luogu3396] 雜湊衝突

題意

Here

思考

很早之前做的這一題,當時覺得這題的根號平衡思想很贊,現在重新回顧一遍,記錄下來。

簡要題意:給你 \(x,p\) ,從 \(x\) 開始,每隔 \(p\) 個數取一個數,求和

暴力的想法是直接列舉,計算,複雜度 \(O(n^2)\),當然我們也可以對答案進行簡單的預處理,令 \(ans[p][i]\) 表示模數是 \(p\),餘數是 \(i\)的答案,但預處理複雜度同樣是 \(O(n^2)\), 但我們實現 \(O(1)\) 詢問了,期望得分 \(10\)

我們得到的兩種演算法,第一種不用預處理,但詢問是 \(O(n^2)\) 的,第二種 \(O(n^2)\) 的預處理,但詢問是 \(O(1)\)

的,我們考慮能否將這兩種演算法中和一下:只預處理 \(p\in[1,\sqrt n]\)\(ans[p][i]\),預處理的複雜度變為了 \(O(n\sqrt n)\),考慮詢問,若單次詢問的 \(p \leq\sqrt n\),那麼它是 \(O(1)\) 的,若 \(p > \sqrt n\),我們暴力統計,易知少於 \(\sqrt n\) 個數對答案有貢獻,那麼詢問的複雜度是 \(O(\sqrt n)\) 的,對於修改操作,我們只需要修改 \(ans[][]\)陣列,複雜度\(O(\sqrt n)\), 總體複雜度 \(O((n+m)\sqrt n)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N = 150050;
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*10+ch-'0';ch=getchar();}
    return x * f;
}
int n, m;
int ans[110][110];/// ans[p][i] 對p取模 餘數為i
int val[N], MAX, t;
int query(int p, int la){
    if(p <= t) return ans[p][la];
    int ANS = 0;
    for(int i=la; i<=n; i+=p){
        ANS += val[i];
    }
    return ANS;
}
void change(int pos, int v){
    for(int j=1; j<=t; j++){
        ans[j][pos % j] = ans[j][pos % j] - val[pos] + v;
    }
    val[pos] = v;
}
int main(){
    n = read(); m = read();
    t = sqrt(n);
    for(int i=1; i<=n; i++) val[i] = read();
    for(int i=1; i<=n; i++){
        for(int j=1; j<=t; j++){
            ans[j][i % j] += val[i];
        }
    }
    while(m --){
        char a; cin >> a;
        int x, y; cin >> x >> y;
        if(a == 'A'){
            cout<<query(x, y)<<endl;
        }
        else change(x, y);
    }
    return 0;
}

總結

本題的根號平衡思想真的很好,通過對小於 \(\sqrt n\) 和大於 \(\sqrt n\) 的範圍進行分治,將複雜度優化成根號