1. 程式人生 > 實用技巧 >3種方式實現Redis限流

3種方式實現Redis限流

字首和與差分

一維字首和

1、用途

\(O(1)\)區間查詢

2、原理

\(S[n]\)為數列\(a\)的前\(n\)項和,那麼\(a[l:r]\)的和為\(S[r]-S[l-1](1\le l\le r\le n)\)

轉移方程:\(S[i]=S[i-1]+a[i]\)

3、複雜度

預處理:\(O(n)\)

查詢:\(O(1)\)

4、模板

for (int i = 1; i <= n; ++i) S[i] = S[i - 1] + a[i]; //build
cout << S[r] - S[l - 1] << endl; //query

5、備註

①下標一定從1開始。

二維字首和

1、用途

\(O(1)\)查詢子矩陣和

2、原理

設以\((1,1),(i,j)\)為主對角線兩個端點的子矩陣的元素和為\(S[i][j]\)。那麼以\((x_1,y_1),(x_2,y_2)\)為主對角線兩個端點的子矩陣和為\(S[x_2][y_2]-S[x_1-1][y_2]-S[x2][y_1-1]+S[x_1-1][y_1-1]\),其中\(1\le x_1\le x_2\le i,1\le y_1\le y_2\le j\)

轉移方程:\(S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j]\)

用圖表示比較直觀。

假設我們要求紅色部分的和。

那麼我們可以先計算出整個綠色區域的和。

之後減去不必要的部分,這裡用黃色表示。

但是注意到灰色部分減去了兩次,因此加上一次。這就是查詢的原理。

轉移方程也類似。

3、複雜度

預處理:\(O(mn)\)

查詢:\(O(1)\)

4、模板

for (int i = 1; i <= n; ++i) //build
    for (int j = 1; j <= m; ++j)
        S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + a[i][j];
cout << S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][x1 - 1] << endl; //query

5、備註

①下標一定從1開始。

一維差分

1、用途

區間修改,區間查詢

2、原理

假設對\(a[l:r]\)同時進行\(+d(d\in \mathbb R)\)操作,觀察到\(\forall i\in (l,r],a[i]-a[i-1]\)不變,僅有\(a[l]-a[l-1]\)相比原來的值多\(d\),並且\(a[r+1]-a[r]\)相比原來的值少\(d\)

於是定義差分陣列\(dif[i]\),差分陣列的字首和為\(sum[i]\),修改後的元素大小為\(a'[i]=a[i]+sum[i]\)

\(sum[i]\)可以看成\(a[i]\)的增量。

\(a'[l]=a[l]+sum[l],a'[l-1]=a[l-1]+sum[l-1]\),兩式相減得\(dif[l]=d\)。同理\(dif[r+1]=-d\)

於是\([l,r]\)區間修改可以看成\(dif[l]+=d,dif[r+1]-=d\)。查詢時只要對\(dif\)求字首和,再加上原來的值即可。

畫圖比證明直觀。圖與下面二維差分類似,就不另畫了。

3、複雜度

區間修改:\(O(1)\)

區間查詢:\(O(n)\)

4、模板

dif[l] += d, dif[r + 1] -= d; //build
for (int i = 1, s = 0; i <= n; ++i)
    s += dif[i], cout << a[i] + s << endl; //query

5、備註

①下標一定從1開始。

②差分陣列要比給的資料上限開得更大一點。

③由於區間查詢複雜度比較高,一般在多修改少查詢的情況下使用。

④只支援簡單的加減修改操作。

二維差分

1、用途

子矩陣修改,子矩陣查詢

2、原理

類比一維差分定義二維差分陣列\(dif[i][j]\),以差分陣列的二維字首和表示\((i,j)\)的增量。

考察修改差分陣列的值對原陣列的貢獻。若\(dif[i][j]+=d\),則對於\(\forall x,y\),若滿足\(x\ge i\)並且\(y\ge j\),則\((x,y)\)處的值就都要加上\(d\)

以圖為例,假設表格表示差分陣列。現在用紅色表示在該格處進行修改。

那麼該格對於差分陣列的貢獻就是所有綠色的格子。

如果我們要修改如下圖所示的紅色區域:

那麼與二維字首和類似,我們可以先修改綠色的區域,再減去兩個黃色的區域,最後補上減去兩次的灰色區域。每次修改都在色塊的左上角方格進行。

3、複雜度

子矩陣修改:\(O(1)\)

子矩陣查詢:\(O(mn)\)

4、模板

dif[x1][y1] += d, dif[x2 + 1][y1] -= d, dif[x1][y2 + 2] -= d, dif[x2 + 1][y2 + 1] += d; //build
for (int i = 1; i <= n; ++i) //query
    for (int j = 1; j <= m; ++j)
    {
        dif[i][j] += dif[i - 1][j] + dif[i][j - 1] - dif[i - 1][j - 1];
        cout << a[i][j] + dif[i][j] << endl;
    }

5、備註

①下標一定從1開始。

②差分陣列要比給的資料上限開得更大一點。

③由於子矩陣查詢複雜度比較高,一般在多修改少查詢的情況下使用。

④只支援簡單的加減修改操作。