到底收了几份问卷?

2025 年 3 月 17 日 星期一(已编辑)
25
AI 生成的摘要

到底收了几份问卷?

众所周知,收集问卷是个受累不讨好的活,在过去线下收集问卷自然是非常麻烦,而线上问卷收集依然不是一个非常轻松的活。就以我个人为例,扩大到最大的影响力也不过是收集到了155份问卷,而且这个问卷还是以单选题为主。

Forms数据

Forms数据

自然就有人动歪脑筋,反正最后也是算一个百分比,把数字按比例扩大,百分比照样不变,还能显出自己的工作量之大,计算量也小了不少,可谓是两难自解。

但是,古尔丹,代价是什么呢?

互质

让我们引入一个简单的概念——互质。

若两个非零整数的最大公约数为1(即它们的公因数只有1),则称这两个数互质。互质有一些非常有趣的性质

  • 与1的关系 :1与任何整数互质,因为1的因数只有自身。
  • 质数特性 :不同质数必然互质,如3和5。
  • 相邻数规律 :相邻的两个自然数(如8和9)或相邻奇数(如15和17)一定互质。
  • 判断方法 :可以通过计算最大公约数(如欧几里得算法)判断两个数是否互质。

如果把数字按比例扩大,分子分母同时扩大,虽然百分比不变,但是分子与分母就必然不可能互质。或者反过来说,只要找到一对互质的数,使得相除后的结果接近算出的百分比,就可以大致得出真实的问卷数量,任何放缩,必将绳之以法!

如何操作

我们假设问卷数为NN,百分比大小为a1,a2,a3,ana_1,a_2,a_3,\cdots a_na1,a2,a3,ana_1,a_2,a_3,\cdots a_n对应的分子为b1,b2,b3,bnb_1,b_2,b_3,\cdots b_n,即a1b1N,a2b2NanbnNa_1\approx\frac{b_1}{N},a_2\approx\frac{b_2}{N}\cdots a_n\approx\frac{b_n}{N},一般百分比保留两位小数,所以我们的精度必须小于0.00010.0001,每一个b1b_1NN都是整数,所以我们就可以带入一些数字硬算了。

当然也可以培养一下对于数字的敏感性,简单点说可以关注1某数\frac{1}{某数}的值,1某数\frac{1}{某数}的分子分母必然互质,属于是非常显眼的特征。比如说看到13\frac{1}{3}就差不多能猜测问卷一共只有3份,估计是自己填了一份,自己朋友填一份,再找异性填一份,还可以兼顾性别比例。

深一点想的话,看到0.0222、0.1556、0.1778、0.4667就能迅速反应过来这些数是

1450.02222,误差0.00002 \frac{1}{45}≈0.02222,误差 0.00002
7450.15556,误差0.00004 \frac{7}{45}​≈0.15556,误差 0.00004
8450.17778,误差0.00002 \frac{8}{45}≈0.17778,误差 0.00002
21450.46667,误差0.00003 \frac{21}{45}​≈0.46667,误差 0.00003

言及于此,不由得感慨这个问题实在是简简又单单啊,稍微一瞪眼就看出来结果了,so easy啊,写了这么长,文章也该结束了吧?

那当然不可能,感谢AI,完善了我的思路,甚至更进一步,实在是好上加好,解放了我的大脑,思路非常简单,数字也不是很大,暴力就得了。

import math

from itertools import product

from functools import reduce

  

def find_min_n_direct(decimal_list, tolerance=0.0001, max_n=10000):

    """

    直接搜索法:从小到大尝试每个整数n,直到找到符合条件的最小值

    参数:

        decimal_list: 小数列表

        tolerance: 误差容忍度,相对于n的值

        max_n: 搜索的最大n值

    返回:

        满足条件的最小整数n,如果未找到则返回None

    """

    for n in range(1, max_n + 1):

        valid = True

        for x in decimal_list:

            k = round(x * n)

            error = abs(x * n - k) / n

            if error > tolerance:

                valid = False

                break

        if valid:

            return n

    return None

  

# 用户输入

m = int(input("请输入小数的个数 m: "))

decimals = [float(input(f"请输入第 {i+1} 个小数: ")) for i in range(m)]

  

# 执行搜索

result = find_min_n_direct(decimals)

  

# 输出结果

if result:

    print(f"满足条件的最小整数 n = \033[1;32m{result}\033[0m")

    # 打印详细信息

    print("\n验证结果:")

    for x in decimals:

        k = round(x * result)

        error = abs(x * result - k)

        print(f"{x} × {result} = {x*result:.4f} ≈ {k} (误差: {error:.4f})")

else:

    print("未找到符合条件的 n。")

到了这里就要结束了吗?当然不会,肯定有人会说如果我的问卷的数据非常恰好,就是没有任何一个分子与总问卷数互质,这样算出来的数据就会小于总问卷数,没有参考性。

如果这样的话,那只能说是非常巧了,我们就需要引入另一个概念了。

欧拉函数

欧拉函数(Euler's totient function),记作 (φ(n)\varphi(n)),是数论中用于计算小于等于正整数nn且与nn互质的正整数的数目的函数.

定义

对正整数nnφ(n)\varphi(n)表示在11nn之间与nn互质的数的个数。例如:

  • φ(8)=4\varphi(8) = 4,因为1,3,5,71, 3, 5, 788互质。
    • φ(12)=4\varphi(12) = 4,因为1,5,7,111, 5, 7, 111212互质。
  • 特殊值φ(1)=1\varphi(1) = 1,因为11与自身互质。

核心性质

  1. 积性函数 

   若aabb互质,则φ(ab)=φ(a)φ(b)\varphi(ab) = \varphi(a)\varphi(b)

  1. 质数的欧拉函数 

   若pp为质数,则φ(p)=p1\varphi(p) = p-1,因为所有小于pp的数均与pp互质。

  1. 质数幂次的欧拉函数 

   若pp为质数,kk为正整数,则φ(pk)=pkpk1\varphi(p^k) = p^k - p^{k-1}

   例如,φ(9)=φ(32)=93=6\varphi(9) = \varphi(3^2) = 9 - 3 = 6

  1. 通用计算公式 

   若nn的质因数分解为n=p1k1p2k2pmkmn = p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m},则:

φ(n)=n(11p1)(11p2)(11pm) \varphi(n) = n \left(1 - \frac{1}{p_1}\right)\left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right)

   例如,n=12=2231n = 12 = 2^2 \cdot 3^1,则:

φ(12)=12(112)(113)=121223=4 \varphi(12) = 12 \left(1 - \frac{1}{2}\right)\left(1 - \frac{1}{3}\right) = 12 \cdot \frac{1}{2} \cdot \frac{2}{3} = 4

图像

欧拉函数的图像大致是这个样子。

Figure_1

Figure_1

nn的质因数分解为n=p1k1p2k2pmkmn = p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m},根据φ(n)=n(11p1)(11p2)(11pm)\varphi(n) = n \left(1 - \frac{1}{p_1}\right)\left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right)这个公式可以通过以下的代码画出这个函数

import matplotlib.pyplot as plt

import numpy as np

  

def euler_phi(n):

    """计算欧拉函数φ(n):小于或等于n且与n互质的正整数的个数"""

    result = n  # 初始化为n

    # 检查所有可能的质因数

    p = 2

    while p * p <= n:

        if n % p == 0:

            # p是n的一个质因数

            while n % p == 0:

                n //= p

            result -= result // p  # φ(n) = φ(n) * (1 - 1/p)

        p += 1

    # 如果n最后大于1,则n是一个质数

    if n > 1:

        result -= result // n

    return result

  

# 计算从1到1000的欧拉函数值

n_values = np.arange(1, 1001)

phi_values = [euler_phi(n) for n in n_values]

  

# 创建图像

plt.figure(figsize=(12, 8))

plt.scatter(n_values, phi_values, s=5, alpha=0.6)  # 减小点的大小,增加可读性

plt.plot(n_values, phi_values, 'r-', alpha=0.2, linewidth=0.5)  # 淡化连线

  

# 添加图像标题和标签

plt.title(' φ(n) (1-1000)')

plt.xlabel('n')

plt.ylabel('φ(n)')

plt.grid(True, alpha=0.3)

  
  

plt.legend(['φ(n)'], loc='upper right')

  

# 显示图像

plt.tight_layout()

plt.show()

通过图像,不难看出

φ(n)\varphi(n) 的取值为整数,图像由离散点组成,但通常以折线或散点图形式呈现连续趋势 nn为质数时φ(p)=p1\varphi(p) = p-1,图像中沿直线 y=x1y = x-1 分布的,如n=11n=11φ(11)=10\varphi(11)=10
nn为幂次合数时,如n=9=32n=9=3^2φ(9)=6\varphi(9)=6,值较高但低于同范围质数。
nn为多质因数合数时,如n=30=235n=30=2 \cdot 3 \cdot 5φ(30)=8\varphi(30)=8,远低于相邻质数点。

φ(n)n\frac{\varphi(n)}{n}的特点

当然,我们还可以更进一步,看看调查问卷数据有多大可能击中一对非互质的分子分母。还是老性质,若nn的质因数分解为n=p1k1p2k2pmkmn = p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m},则:φ(n)=n(11p1)(11p2)(11pm)\varphi(n) = n \left(1 - \frac{1}{p_1}\right)\left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right)

那么就有φ(n)n=(11p1)(11p2)(11pm)\frac{\varphi(n)}{n} = \left(1 - \frac{1}{p_1}\right)\left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right)

那么就很简单了,假设问卷数nn的质因数有pmp_m,那么pm×np_m \times nnn随机碰撞到互质的分子分母的概率是相同的,如果有谁的问卷的数据非常恰好,就是没有任何一个分子与总问卷数(pm×np_m \times n)互质,那么在更大的数据范围内也有相同的概率就是没有任何一个分子与总问卷数(nn)互质。既然概率相同,你怎么专挑这件事情报道,因此前文所述的情况,明显是可能性很小的。我们这里也不是刑法,不需要保持什么谦益性,这种概率非常小的情况完全可以忽略。

假设plp_l不是问卷数nn的质因数,那么pl×np_l \times nnn随机碰撞到互质的分子分母的概率就大有不同,可以看看nn的质因数,找几个并非nn的质因数,与nn乘起来看看咸淡,挑一个比较合理的就可以。

但是大家也不用因此沮丧,还是看看图像吧。

就有以下的图像

Figure_2

Figure_2

或许范围可以大一些

Figure_3

Figure_3

这样看得就清楚多了。

可以看到,图像呈现明显的分形特性和自相似模式,可以观察到多个周期性下降的结构,每条"支线"对应特定质数的倍数,随着 n 增大,整体趋势呈下降波动,而且随着 n 增大,φ(n)/n 的平均值趋近于 6/π² ≈ 0.6079,这个结果与素数分布的渐近密度相关。(以上是AI生成)

由此可见,既然碰撞概率下降不明显,总而言之这个方法还是比较可信的,可以轻松发现一些没什么诚意的造假。

缺陷

当然这个方法也是有缺陷的,出了上文提到的不严谨之处,由于通常的问卷调查结果是保留两位小数的百分数,所以本代码对误差的tolerance在0.0001,如果问卷数量超过10000份,得出的结果也只有10000,能力就限制到这儿了。如果结果是保留一位小数的百分数,tolerance在0.001,那就是上限1000份,以此类推了。

此外穷举优雅性不足,本来让AI写了一个连分数的算法,结果没过测试,部分情况算出来的数字偏大,反正只有10000个数字,都用python了,就别追求效率,穷举就完了。

最后还有一个缺陷,但我是不会说的,那不是增加我的发现难度吗!

最后用一些知网的文献练练手,让大家看看到底准不准。

OK,收工。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...