隨機演算法

(Randomized Algorithm)

Step 1. 產生亂數

在實際談論隨機演算法前,我們要知道如何產生亂數

你可能學過如下圖最基本的亂數產生

//給予亂數種子
srand(time(NULL));
//生成亂數
rand()%x

電腦的隨機並不是真的隨機!

 

他的原理是從一個決定好的亂數種子產生亂數

 

他可以是電腦的記憶體、執行時間等等

最常見的亂數種子

 

1. time(NULL)

 

2. chrono::steady_clock::now().time_since_epoch().count()

那有了亂數種子之後,我們要生成亂數

 

最好的亂數產生,我們會希望數字的機率是均勻分布的

 

也就是每個數字的出現機率是相等的

以 C++ 的亂數為例

 

rand() 並不是一個均勻分布的隨機函數

 

他所產生的數字也只會在 [1,RAND_MAX] 之間

(RAND_MAX 是 32767)

我們更常使用 mt19937

實作方式為梅森旋轉算法

他是一個均勻分布的偽隨機亂數產生算法

宣告方式如下

//使用 time(NULL) 作為隨機種子
mt19937 rng(time(NULL);

//使用高精度時間函數作為隨機種子
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())

Step 2. 隨機的正確性

思考一下,假設今天有個演算法

 

他能夠在 \(p\) 的機率找到正確答案

例如 \(p=0.5\) 等等的數字

這個機率看似不高,但是否表示無法成為正確的算法呢

範例

 

有個題目問你是否能夠構造出某個解

你確定你有 \(20\%\) 的機率隨機產生成功的答案

機率看似不高,不過真的是這樣嗎?

範例

 

\(20\%\) 雖然感覺很低

不過如果我們重複嘗試同樣的事情呢

 

5 次都失敗的機率 => \(0.8^5 = 0.327\)

10 次都失敗的機率 => \(0.8^{10} = 0.107\)

20 次都失敗的機率 => \(0.8^{20} = 0.011\)

30 次都失敗的機率 => \(0.8^{30} = 0.001\)

 

也就是跑 30 次時,有 \(99.9\%\) 的機率找到正確的答案!

來看一個經典例題

你有一個 \(n\) 項的陣列,與 \(q\) 次詢問

每次詢問問你在 \([l,r]\) 區間內的絕對眾數

 

*絕對眾數: 當一個數字 \(x\) 出現次數  \( \ge \lceil\frac n 2\rceil\) ,即為絕對眾數

做法

 

如果有絕對眾數,戳一個數字有 \(50\%\) 的機率得到

因此多跑幾次就可以了

接著再檢查次數是否 \(> \lceil \frac n 2 \rceil\)

Step 3. 另一種隨機  雜湊

(Hash)

這種方式比較不一樣

我們會給定一種東西叫做 Hash

將某個輸入經過某個黑盒子

得到另外一種東西

之後再利用這個東西去比對答案

 

 

 

\(abcde\)

 

\(p = 31, m = 10^9+7\)

1*p^4+2*p^3+3*p^2+4*p+5 \pmod{10^9+7}

生日攻擊

 

23 人 => \(50\%\) 兩個同一天生日

 

Hash \(m = 10^9+7\)

\(10^5\) 個字串

可能會碰撞!

 

第一個 hash => \(m =10^9+7\)

第二個 => \(m=998244353\)

範例

 

今天給你一個陣列

有 \(q\) 個詢問

每次詢問一個區間內是否每個數字都出現偶數次

範例

 

今天給你一個陣列

有 \(q\) 個詢問

每次詢問一個區間內是否每個數字都出現偶數次

\([1,2,3]\) XOR 會錯

\(5, 9, 10\)

暴力 => \(O(qC)\)

作法

 

隨機賦予這些數字不同數字

 

然後去看整個區間 XOR 是不是 0

 

使用前綴 XOR 去檢查

 

多跑幾次這方式就會 AC 了

範例

 

今天給你一個陣列

有 \(q\) 個詢問

每次詢問一個區間內是否每個數字都出現三的倍數次

 

[1,1,2,2,2,1,3,3,3]

第一次出現的 x => x

第二次 => x

第三次 => -2x

Made with Slides.com