1. 程式人生 > >微信紅包.....真的是搶的越晚越好嗎

微信紅包.....真的是搶的越晚越好嗎

微信紅包.....真的是搶的越晚越好嗎


大家都喜歡搶紅包這是必然的…雖然日常都是0.01….
在寫這篇沒有什麼大意義的文章之前,先給出我程式碼跑了7000W次的結果…

騰訊作為一個有腦子的公司,那麼多大佬在團隊裡,演算法肯定要比我個人寫的更加完整和完善…所以首先可以保證…搶到紅包的均值是肯定一樣的,因為期望值一定是 <總錢數>/<總人數>


那麼存在的區別僅僅是根據個人的風險偏好的結構(risk preference structure)不同,風險迴避(risk-averse)的抽取者應該儘量先抽,而風險偏愛(risk-seeking)的抽取者應該儘量後抽。

即喜歡刺激的人可以留到快搶完的時候搶,喜歡穩定的自然是第一時間搶,因為測試證明,在7000W次模擬中,越後搶的人有更大的機率抽到最大的紅包,但同樣也有更大的機率抽到均值以下的紅包,即所謂的搏一搏單車變摩托

說了這麼多,請注意一個問題,這一切的前提都是…..

你能搶的到紅包,否則一切都是廢話


1. 首先先給出一個有意思的小測試….

如果你發出n個紅包總金額是(n+1)分,那麼0.02這個手氣王必然在最後兩人之中,而有99.99%以上的概率是最後一個人…

2. 微信紅包的發放演算法

  1. 第一個問題必然是紅包的發放方式,雖然沒有意義,但是可以先小討論一下...其實沒啥好說的,要麼就是每次有人搶紅包的時候在剩餘錢數中隨機出金額,要麼就是在發出紅包之前就已經計算完每個人應該獲得的錢,然後按順序pop罷了,那我個人當然認為是第二種..因為我認為實時計算這種方式非常蠢...
  2. 第二個問題就是紅包發放演算法了,首先可以總結出幾點:
  3. 1)所有人都能分到紅包,也就是不會出現紅包數值為0的情況。
  4. 2)所有人的紅包數值加起來等於支付的金額。
  5. 3)紅包波動範圍比較大,約5%~8%的紅包數值在平均值的兩倍以上,同時數額0.01出現的概率比較高。
  6. 4)紅包的數值是隨機的,並且數值的分佈近似於正態分佈。
  7. 所以很多人會想到正態分佈,比如分50元錢給25個人,那麼我只要均勻隨機出24個在0-50之間的數,這24個數可以將50元分成25分,然後按順序給出去即可,那麼這種發放方式的期望值雖然也是M/N,但是其分佈是指數分佈,也就是多會造成大多數人拿到的錢非常少,而極少數人會獲得很多錢的情況,即方差非常大,這肯定不是大家想看到的或者是微信想看到的。
  8. 因為每個紅包的最小值為0.01,所以在初始的時候為每個紅包預留0.01元,那麼剩餘金額總數為M-0.01*k。
  9. 那麼易得 < ai = rand(T - 0.01 * k - a0 - ... - ai-1) >
  10. 紅包數額的分佈並不完全符合正態分佈,因為每個紅包的數額都有上限和下限,所以準確地說應該是截尾正態分佈,在這裡紅包金額範圍為[0, Mi]。
  11. 剩下要做的就是確定sigma的數值,sigma的值會直接影響紅包數額的分佈曲線。根據正態分佈的三個sigma定理, 生成的隨機數值有95.449974%機率落在(mu-2*sigma,mu+2*sigma)內
  12. 為了使得mu-2*sigma = 0,sigma = mu/2。對於生成的隨機數落在[0, Mi]以外區間的情況,採用截斷處理,統一返回0或者Mi。也就是說,最後生成的隨機數值分別有大約6%的機率為0或者大於2*(Mi/(n-i)),加上保留的0.01
  13. 所以程式碼在底部

3. 最佳手氣的金額對比

經過測試可以發現,最佳手氣金額在人數增加的時候,趨於穩定,並維持在平均值的兩倍左右

4. 不同位置搶紅包的均值

以下是20元紅包15個人搶1000次的人均價值,可以發現,不管是第幾個搶到的紅包,搶到手的均值是不會變的,都是趨向於<總錢數>/<總人數>

5.是20元紅包15個人搶1000次

以下是每個位置的人搶1000次20元紅包的金額分佈
手動增加了快進功能….









在最後一張十五個人的擬合曲線中,我們可以看到越往後的位置獲得的金額曲線更加像指數分佈,也就是越靠近最基本的正態分佈的方式,即最大金額越大,但是曲線更平坦,也就是開頭說到的,越往後搶紅包有機率獲得更大的紅包,但也會有更大的機率獲得比一般人低的紅包,也就是收穫和風險並存?

然後就是我們的開頭的結論了,若是你想要搏一搏…自然是留到後面搶比較好,若是你想求穩,那麼自然是早點搶到手的好….

【程式碼】

#include <bits/stdc++.h>
using namespace std;
#define TWO_PI 6.2831853071795864769252866
double generateGaussianNoise(const double mu, const double sigma)
{
    using namespace std;
    static bool haveSpare = false;
    static double rand1, rand2;
    if(haveSpare)
    {
        haveSpare = false;
        return (sigma * sqrt(rand1) * sin(rand2)) + mu;
    }
    haveSpare = true;
    rand1 = rand() / ((double) RAND_MAX);
    if(rand1 < 1e-100) rand1 = 1e-100;
    rand1 = -2 * log(rand1);
    rand2 = (rand() / ((double) RAND_MAX)) * TWO_PI;
    return (sigma * sqrt(rand1) * cos(rand2)) + mu;
}
vector<double> generateMoneyVector(const double mon, const int pics)
{
    vector<double> valueVec;
    double moneyLeft = mon - pics * 0.01;
    double mu, sigma;
    double noiseValue;
    for(int i = 0; i < pics - 1; i++)
    {
        mu = moneyLeft / (pics - i);
        sigma = mu / 2;
        noiseValue = generateGaussianNoise(mu, sigma);
        if(noiseValue < 0) noiseValue = 0;
        if(noiseValue > moneyLeft) noiseValue = moneyLeft;
        noiseValue += 0.005;
  noiseValue = ((int)(noiseValue * 100))/100.0; 
        valueVec.push_back(noiseValue + 0.01);
        moneyLeft -= noiseValue;
    }
    valueVec.push_back(moneyLeft + 0.01);
    return valueVec;
}
vector<double> f[10000];
int main()
{
 freopen("1.txt","w",stdout);
 double mon;
 int pics;
 for (int i=0;i<1000;i++)
 {
  mon=20;
  pics=15;
  vector<double> a=generateMoneyVector(mon,pics);
  for (int i=0;i<a.size();i++) f[i].push_back(a[i]); 
 }
 for (int j=0;j<15 ;j++)
 {
  double sum=0;
  sort(f[j].begin(),f[j].end());
  for (int i=0;i<f[j].size();i++) 
  {
   sum+=f[j][i];
   printf("%.2f\t",f[j][i]);
  }
  sum/=1000;
  printf("%lf\t",sum);
  puts(""); 
 }
 return 0; 
}