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開始。
②差分陣列要比給的資料上限開得更大一點。
③由於子矩陣查詢複雜度比較高,一般在多修改少查詢的情況下使用。
④只支援簡單的加減修改操作。