题目描述
给定一个金额和人数,实现微信抢红包算法。
隐含条件,不能有人是空红包,即每个人最少分得0.01元,同时会确保输入数据合理(微信红包人均低于0.01发不出来)。
问题
由于最少需要0.01元,浮点数随机的时候不太好处理,可以直接金额乘上100,这样分完以后再除以100。另外就是考虑如何随机更公平。
参考
博客中一共提出了四种算法,这里自己又实现了一遍。
-
第一种,纯随机法,会出现先领的人领到的多,后领的人得到的少的问题,该解法不好。
-
第二种,线段法,将总金额想象成一条那么长的线段,需要分割成num份,随机num-1次,将每次的随机值映射到该线段上。个人觉得相对比较公平,但是存在一个问题,如果发的红包比较小,比如9个人分0.1元,该方法效率有点低,因为要防止出现重复的点。
-
第三种,双倍随机法,每次随机的时候,取0-每个人平均金额的2倍进行随机。这种方法有一个问题,不会有人分到超过平均金额的2倍的钱。
-
第四种,投票法(投篮法),即规定一个最小金额,比如0.01元,每次将最小金额随机给一个人。当然,可以先给每个人分0.01元,这样就不会出现有人没分到的情况。这种方法确实比较随机,但计算量稍大,而且只要人均金额稍微大一点,就会出现分布过于平均的情况。个人觉得可以在人均比较小的场景下用,比如9个人分0.1元。
代码
#include<bits/stdc++.h>
using namespace std;
// 纯随机法
void red_packet1(int money_i, int num, vector<int> &res) {
int min_i = 1; // 每个人最少一分钱
int max_i = money_i - min_i * (num - 1); // 每个人最多可以分到的钱
int temp;
for (int i = 0; i < num - 1; ++i) {
temp = rand() % max_i + min_i;
res.push_back(temp);
money_i -= temp;
max_i = money_i - min_i * (num - i - 1);
}
res.push_back(money_i);
}
// 线段法
void red_packet2(int money_i, int num, vector<int> &res) {
set<int> m_set;
int temp;
for (int i = 0; i < num - 1; ) {
temp = rand() % (money_i-1) + 1;
if (!m_set.count(temp)) { // 去重
i++;
m_set.insert(temp);
}
}
int last = 0;
// set已经排好序,直接取值计算即可
for (auto item:m_set) {
res.push_back(item - last);
last = item;
}
res.push_back(money_i - last);
}
// 双倍随机法
void red_packet3(int money_i, int num, vector<int> &res) {
res.resize(num, 1); // 设置保底,每个人最少1分钱
money_i -= 1 * num;
int base_m = money_i / num * 2;
int temp;
for (int i = 0; i < num - 1; ++i) {
if (money_i == 0)
break;
if (money_i >= base_m)
temp = rand() % base_m;
else
temp = rand() % money_i;
res[i] += temp;
money_i -= temp;
}
res[num-1] += money_i;
}
// 投票法
void red_packet4(int money_i, int num, vector<int> &res) {
res.resize(num, 1); // 设置保底,每个人最少1分钱
money_i -= 1 * num;
for (int i = 0; i < money_i; ++i) {
res[rand() % num]++;
}
}
int main( )
{
srand(time(NULL)); // 设置随机种子
double money = 10;
int num = 10;
// cin >> money >> num;
int money_i = money * 100; // 扩大100倍,便于随机处理
vector<int> res;
// red_packet1(money_i, num, res);
// red_packet2(money_i, num, res);
// red_packet3(money_i, num, res);
red_packet4(money_i, num, res);
cout << "result sum:" << (double)accumulate(res.begin(), res.end(), 0) / 100 << endl ; // 验证总金额
for (auto item: res) {
printf("%.2f ", (double)item / 100);
}
cout << endl;
}