LintCode 1496: Implement Rand10() Using Rand7() (随机数生成好题)

本文探讨如何利用rand7函数生成1至7之间的均匀随机整数,来创建一个rand10函数,该函数可以生成1至10之间的均匀随机整数。文章详细解释了一种有效的方法,并对比了一些常见的错误做法,确保生成的随机数分布均匀。

1496. Implement Rand10() Using Rand7()

Given a function rand7 which generates a uniform random integer in the range 1 to 7, write a function rand10 which generates a uniform random integer in the range 1 to 10.

 

Do NOT use system's Math.random().

 

Example

Example 1:

 

Input:1

Output:[7]

Example 2:

 

Input:2

Output:[8,4]

Example 3:

 

Input:3

Output:[8,1,10]

Challenge

What is the expected value for the number of calls to rand7() function?

Could you minimize the number of calls to rand7()?

Notice

rand7` is predefined.

Each testcase has one argument: n, the number of times that rand10 is called.

这题不难但是很容易错!

解法1:

用两次rand7(),第1次先在0, 7, 14, ..., 42之间取,相当于取间隔。第2次在第一次的基础上加上rand7()。

这样生成的sum就是在1..49之间均匀分布的随机数。然后将其截取到1..40,返回(sum-1)/4+1,即1..4返回1, 5..8返回2,...,37..40返回10。

// The rand7() API is already defined for you.
// int rand7();
// @return a random integer in the range 1 to 7
class Solution :public SolBase {
public:
    int rand10() {
        int a, b, sum;
        do
        {
            a = rand7() - 1;
            b = rand7();
            sum = a * 7 + b;
        } while(sum > 40);

        return (sum - 1) / 4 + 1;
    }
};

下面是我之前的做法。后来发现不对。
先调用1次rand7(),然后再调用1次rand7(),看其返回值是不是在1..4之间,若否则继续循环,否则将2次rand7()的返回值加起来。为什么第2次rand7()要看是不是在1..4之间而不是1..3之间呢,因为我们要把第2次rand7()的值减去1,对应到0..3,这样,sum1+sum2-1才对应到1..10。不然就是2..10了。

为什么不对呢?因为这是两个均匀随机变量rand1_7()和rand1_3()的和,结果会倾向于正态分布而不是均匀分布。比如说结果1只有(1,1)这一种可能性,结果6有(4,2),(5,1),(3,3)这3种可能性,结果4有(1,3),(3,1)(2,2)这3种可能性,结果9有(7,2),(6,3)这2种可能性,结果10只有(7,3)这1种可能型。

// The rand7() API is already defined for you.
// int rand7();
// @return a random integer in the range 1 to 7
class Solution :public SolBase {
public:
    int rand10() {
        int sum1 = 0, sum2 = 0;
        sum1 = rand7();
        while ((sum2 = rand7()) > 4) continue;
        return sum1 + sum2 - 1;
    }
};

 

网上看到几种解法,感觉不对。
1)

int rand10() {
return (rand7() + rand7() ) % 10 +1; 
}

这里的结果并不是在1..10上均匀分布。比如返回1的情况只有5+5这1种,概率p=1/49.
返回3的情况有1+1, 6+6这2种,概率p=2/49.
2)

    int rand10() {
         int num = rand7() * 10;
         return num / 7;
    }

这里的结果只会在1,3,4,5,7,8,10上分布。
3)

    int rand10() {
        while (true) {
            int x = rand7();
            int y = rand7();
            int prod = x * y;    
            if (prod > 40) continue;
            return (prod % 10) + 1;
        }
    }

这里的prod也不会在1..49上均匀分布。比如说31根本取不到,而12会取到很多次。12=3*4=4*3=2*6=6*2。
 

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <stdbool.h> typedef long long ll; typedef unsigned long long ull; // 定义椭圆曲线点结构 typedef struct { ll x; ll y; bool is_infinity; // 标记是否为无穷远点 } ECPoint; // 定义椭圆曲线参数 typedef struct { ll p; // 素数模数 ll a; // 曲线参数a ll b; // 曲线参数b ECPoint G; // 生成点 ll n; // G的阶 } ECC_Params; // 函数声明 ll mod(ll a, ll m);// 计算a mod m ll mod_inverse(ll a, ll m);// 扩展欧几里得算法计算模逆元 ECPoint point_add(ECPoint P, ECPoint Q, ECC_Params params);// 椭圆曲线点加法 ECPoint point_multiply(ll k, ECPoint P, ECC_Params params);// 椭圆曲线点乘法 int is_prime(ll n);// 检查是否为质数 ull hash(char *message);// 简单哈希函数 void ecdsa_init(ECC_Params *params);// 初始化椭圆曲线参数 void generate_keypair(ECC_Params params, ll *private_key, ECPoint *public_key);// 生成Schnorr密钥对 void schnorr_sign(ECC_Params params,ll private_key, ECPoint public_key, char *msg,ECPoint *R, ll *s);// Schnorr签名生成 int schnorr_verify(ECC_Params params, char *msg, ECPoint R, ECPoint public_key, ll s);// Schnorr签名验证 void aggregate_sign(ECC_Params params, char *msg,ECPoint *ag_P, ECPoint *ag_R, ll *ag_s);// 组签名 void aggregate_verify(ECC_Params params,char *msg, ECPoint ag_R, ECPoint ag_P, ll ag_s);//聚合签名验证 // 计算a mod m,确保结果为正数 ll mod(ll a, ll m) { ll result = a % m; if (result < 0) result += m; return result; } // 扩展欧几里得算法计算模逆元 ll mod_inverse(ll a, ll m) { ll m0 = m, t, q; ll y = 0, x = 1; if (m == 1) return 0; while (a > 1) { q = a / m; t = m; m = a % m; a = t; t = y; y = x - q * y; x = t; } // 确保x为正数 if (x < 0)x += m0; return x; } // 椭圆曲线点加法 P + Q ECPoint point_add(ECPoint P, ECPoint Q, ECC_Params params) { ECPoint R; R.is_infinity = false; // 处理无穷远点情况 if (P.is_infinity) return Q; if (Q.is_infinity) return P; // 如果P和Q是互逆点,结果为无穷远点 if (P.x == Q.x && mod(P.y + Q.y, params.p) == 0) { R.is_infinity = true; return R; } ll lambda; // 斜率 // 计算lambda if (P.x == Q.x && P.y == Q.y) { // 点加倍,P = Q if (P.y == 0) { R.is_infinity = true; return R;// 无穷远点 } ll numerator = mod(3 * P.x * P.x + params.a, params.p); ll denominator = mod(2 * P.y, params.p); lambda = mod(numerator * mod_inverse(denominator, params.p), params.p); } else { // 不同点,P != Q ll numerator = mod(Q.y - P.y, params.p); ll denominator = mod(Q.x - P.x, params.p); lambda = mod(numerator * mod_inverse(denominator, params.p), params.p); } // 计算结果点坐标 R.x = mod(lambda * lambda - P.x - Q.x, params.p); R.y = mod(lambda * (P.x - R.x) - P.y, params.p); return R; } // 椭圆曲线点乘法 k*P(使用快速加倍-加法算法) ECPoint point_multiply(ll k, ECPoint P, ECC_Params params) { ECPoint R; R.is_infinity = true; // 初始化为无穷远点(单位元) k = mod(k, params.n); // 由于n是G的阶,k可以简化为k mod n while (k > 0) { if (k % 2 == 1) { // 如果当前位为1,将current加到result上 if (R.is_infinity == true) {R = P;} else R = point_add(R, P, params); } // 加倍 P = point_add(P,P,params); // 右移k k = k / 2; } return R; } // 检查是否为质数 int is_prime(ll n) { if (n <= 1) return 0; if (n <= 3) return 1; if (n % 2 == 0 || n % 3 == 0) return 0; for (ll i = 5; i * i <= n; i += 6) { if (n % i == 0 || n % (i + 2) == 0) return 0; } return 1; } // 简单哈希函数 ull hash(char *message) { ull hash = 5381; int c; while ((c = *message++)) hash = ((hash << 5) + hash) + c; // hash * 33 + c return hash; } // 初始化椭圆曲线参数 void ecdsa_init(ECC_Params *params) { params->p = 23; // 素数模数 params->a = 1; // 曲线参数a params->b = 4; // 曲线参数b,曲线方程: y² = x³ + x + 4 mod 23 params->G.x = 0; // 生成点x坐标 params->G.y = 2; // 生成点y坐标 params->G.is_infinity = false; params->n = 29; // G的阶 printf("椭圆曲线参数初始化:\n"); printf("p = %lld, a = %lld, b = %lld\n", params->p, params->a, params->b); printf("生成点G: (%lld, %lld)\t", params->G.x, params->G.y); printf("n = %lld\n", params->n); } // 生成Schnorr密钥对 void generate_keypair(ECC_Params params, ll *private_key, ECPoint *public_key) { // 私钥d: 1 < d < n *private_key = rand() % (params.n - 2) + 2; // 公钥Q = d * G *public_key = point_multiply(*private_key, params.G, params); printf("生成密钥对:\n"); printf("私钥 d = %lld\n", *private_key); printf("公钥 Q = (%lld, %lld)\n", public_key->x, public_key->y); } // Schnorr签名生成 // 选择一个随机数k,令R=kG // 令s=k+xH(m||R||P) void schnorr_sign(ECC_Params params,ll private_key, ECPoint public_key, char *msg,ECPoint *R, ll *s) { ll k = rand() % (params.n - 1) + 1; // 随机数k *R = point_multiply(k, params.G, params); // 计算 e = H(m || R.x || R.y || P.x || P.y) mod n char buffer[1024]; sprintf(buffer, "%s%lld%lld%lld%lld", msg, (*R).x, (*R).y,public_key.x,public_key.y); ll e = hash(buffer) % params.n; *s = mod((k + private_key * e), params.n); printf("签名生成\n"); // printf("随机数 k: %lld\n", k); printf("临时点 R: (%lld, %lld)\n", (*R).x, (*R).y); // printf("哈希值 e: %lld\n", e); printf("签名值 s: %lld\n", *s); } // Schnorr签名验证 //sG=R+P⋅H(m||R||P) int schnorr_verify(ECC_Params params, char *msg, ECPoint R, ECPoint public_key, ll s) { // 计算 e = H(m || R.x || R.y || P.x || P.y) mod n char buffer[1024]; sprintf(buffer, "%s%lld%lld%lld%lld", msg, R.x,R.y,public_key.x,public_key.y); ll e = hash(buffer) % params.n; ECPoint sG = point_multiply(s,params.G,params); ECPoint eP = point_multiply(e, public_key, params); ECPoint reP = point_add(R, eP, params); // printf("验证过程\n"); // printf("计算 sG: (%lld, %lld)\n", sG.x, sG.y); // printf("计算 eP: (%lld, %lld)\n", eP.x, eP.y); // printf("计算 reP: (%lld, %lld)\n", reP.x, reP.y); // 验证 sG == R + eP bool valid = false; if (sG.is_infinity && reP.is_infinity) { valid = true; // 两者都是无穷远点 } else if (!sG.is_infinity && !reP.is_infinity) { valid = (sG.x == reP.x) && (sG.y == reP.y); } printf("验证结果: %s\n", valid ? "成功" : "失败"); return valid; } // 组签名 // 私钥:x_1,x_2,对应的公钥P_1=x_1 G,P_2=x_2 G, // 随机数:k_1,k_2,并有R_1=k_1 G,R_2=k_2 G, // 组公钥:P=P_1+P_2 // 组签名:(R, s) R = R_1 + R_2, s = s_1 + s_2 void aggregate_sign(ECC_Params params, char *msg,ECPoint *ag_P, ECPoint *ag_R, ll *ag_s) { printf("聚合签名\n"); printf("message : %s\n", msg); ll x_1, x_2, s_1, s_2, k_1, k_2; ECPoint P_1, P_2, R_1, R_2; generate_keypair(params, &x_1, &P_1); generate_keypair(params, &x_2, &P_2); // 计算组公钥 P = P1 + P2 *ag_P = point_add(P_1, P_2 ,params);//组公钥 printf("ag_P = (%lld,%lld)\n",(*ag_P).x,(*ag_P).y); // 生成随机数并计算 R1 和 R2 k_1 = rand() % (params.n - 1) + 1; R_1 = point_multiply(k_1, params.G, params); k_2 = rand() % (params.n - 1) + 1; R_2 = point_multiply(k_2, params.G, params); *ag_R = point_add(R_1, R_2, params); printf("ag_R = (%lld,%lld)\n",(*ag_R).x,(*ag_R).y); // 计算 e = H(m || R.x || R.y || P.x || P.y) mod n char buffer[1024]; sprintf(buffer, "%s%lld%lld%lld%lld", msg, (*ag_R).x, (*ag_R).y,(*ag_P).x,(*ag_P).y); ll e = hash(buffer) % params.n; // printf("聚合哈希 e: %lld\n", e); // 计算各签名者s值并聚合 s_1 = mod(k_1 + e * x_1, params.n); s_2 = mod(k_2 + e * x_2, params.n); *ag_s = mod(s_1 + s_2, params.n); printf("ag_s = %lld\n", *ag_s); } //聚合签名验证 void aggregate_verify(ECC_Params params,char *msg, ECPoint ag_R, ECPoint ag_P, ll ag_s) { int valid = schnorr_verify(params, msg, ag_R, ag_P, ag_s); // 验证 printf("聚合签名最终验证结果: %s\n", valid ? "签名有效" : "签名无效"); } int main() { // 初始化随机数生成器 srand(time(NULL)); // 1. 初始化椭圆曲线参数 ECC_Params params; ecdsa_init(&params); // 2. 生成密钥对 ll private_key; ECPoint public_key; generate_keypair(params, &private_key, &public_key); // 3. 待签名的消息 char original_message[] = "测试消息,用于签名演示"; // char original_message[1024] = {0}; //明文 // printf("请输入要加密的信息: "); // fgets(original_message, sizeof(original_message), stdin); // original_message[strcspn(original_message, "\n")] = '\0';// 去除换行符 printf("原始消息: %s\n", original_message); // 4. 对消息进行签名 ll s; ECPoint R; schnorr_sign(params, private_key,public_key,original_message, &R, &s); // 生成签名 int valid = schnorr_verify(params, original_message, R, public_key, s); // 验证 printf("最终验证结果: %s\n\n", valid ? "签名有效" : "签名无效"); ECPoint ag_P, ag_R; ll ag_s; aggregate_sign(params, original_message, &ag_P, &ag_R, &ag_s); aggregate_verify(params, original_message, ag_R, ag_P, ag_s); return 0; } 已知上述代码,使用上述代码并进行简单编程,完成ZSS签名,ZSS中 双线性配对模拟 详细执行 使用c语言实现过程
最新发布
09-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值