代码输出满足条件
的(i,j)对的数量(2^j-1) % (2^i-1) == 0
其中
1<=i<j<=n
n是用户输入的数字,在该数字下将找到(i,j)对的数量。 代码工作得很好,但是这段代码背后的逻辑很难理解。
P.S:t是一个变量,它允许用户一次输入多个数字。
#include<stdio.h>
#include<math.h>
int main()
{
int t;
long n,sum,ans;
scanf("%d",&t);
while(t--)
{
scanf("%ld",&n);
int nrt=(int)sqrt(n);
sum=0;
for(int i=1;i<=nrt;i++)
{
sum+=n/i;
}
ans=2*sum-nrt*nrt-n;
printf("%ld\n",ans);
}
return 0;
}
答案 0 :(得分:5)
让我们采用蛮力方法解决问题并打印结果*:
############################# 2^^1 -1 == 1
-#-#-#-#-#-#-#-#-#-#-#-#-#-# 2^^2 -1 == 3
--#--#--#--#--#--#--#--#--# 2^^3 -1 == 7
---#---#---#---#---#---#-- 2^^4 -1 == 15
----#----#----#----#----# 2^^5 -1 == 31
-----#-----#-----#-----# 2^^6 -1 == 63
------#------#------#-- 2^^7 -1 == 127
-------#-------#------ 2^^8 -1 == 255
--------#--------#--- 2^^9 -1 == 511
---------#---------# 2^^10 -1 == 1023
----------#-------- 2^^11 -1 == 2047
-----------#------ 2^^12 -1 == 4095
------------#---- 2^^13 -1 == 8191
-------------#-- 2^^14 -1 == 16383
--------------# 2^^15 -1 == 32767
-------------- 2^^16 -1 == 65535
------------- 2^^17 -1 == 131071
... ...
哈希标记表示满足条件的情况。一个很好的模式出现了:你的每个数字都可以被1整除,每个数字都可以被3整除,每三个数字可被7整除,依此类推。每个i
个数字都可以被2^^i - 1
整除。**
有了这种洞察力,我们可以将您的功能编码为:
int f(int n)
{
int sum = 0;
int i;
for (i = 1; i <= n; i++) sum += (n - i) / i;
return sum;
}
我们可以将(n - i) / i
替换为n / i - 1
,并将公共子标记-1
移动到返回值中:
int g(int n)
{
int sum = 0;
int i;
for (i = 1; i <= n; i++) sum += n / i;
return sum - n;
}
现在让我们看看总和∑(1, n: n / i)
。例如:
∑(i = 1, 9: 9 / i) = 9 + 4 + 3 + 2 + 1 + 1 + 1 + 1 + 1
我们可以通过从右到左查看它并计算每个加数发生的频率来获得相同的总和:
∑(i = 1, 9: 9 / i) = 5*1 + 1*2 + 1*3 + 1*4 + 1*9
我们可以轻松获得这种表述:
∑(i = 1, n: n / i) = ∑(1, n: i * (n / i - n / (i + 1))
这实际上只是写这笔钱的另一种方式;你可以通过不同的方式对这些命令进行分组,以便它们共享相同的分母:
∑(i = 1, N: i * (n / i - n / (i + 1))
= n + ∑(i = 1, n: ((i + 1) - i) * n / (i + 1))
= n + ∑(i = 1, n: n / (i + 1)) - (N + 1) * (n / (N + 1))
= n + ∑(i = 2, n + 1: n / i) - c
= ∑(i = 1, n: n / i) - c
附加字词c = (N + 1) * (n / (N + 1))
是一个更正字词,因为只使用了i = n + 1
的一半字词。在整个范围内求和时,n / (n + 1)
为零并消失。当仅对数组的一部分求和时,它不会消失,我们稍后会看到。
如果我们将总和分成s = sqrt(n)
的头尾,我们得到:
∑(i = 1, n: n / i) = ∑(i = 1, s: n / i) + ∑(s + 1, n: n / i)
让我们以原始的方式表示头部,以“计算加数”的方式表示尾部,例如:
∑(i = 1, 9: 9 / i) = (9 + 4 + 3) + (5*1 + 1*2)
对于任何n
:
∑(i = 1, n: n / i)
= ∑(i = 1, s: n / i) + ∑(1, s - 1: i * (n / i - n / (i + 1))
= ∑(i = 1, s: n / i) + ∑(1, s: n / i) - s * (n / s)
所有除法都是整数除法(这就是为什么必须有括号)和n / s == s
,所以:
∑(1, n: n / i) = ∑(i = 1, s: n / i) + ∑(i = 1, s: n / i) - s * (n / s)
= 2 * ∑(i = 1, s: n / i) - s²
这会产生你原来的功能:
int h(int n)
{
int nrt = sqrt(n);
int sum = 0;
int i;
for(i = 1; i <= nrt; i++) sum += n/i;
return 2 * sum - nrt * nrt - n;
}
上面∑(1, n: n / i)
中的g
已被2 * ∑(i = 1, s: n / i) - s²
替换。
*)我在这里偷了D's power operator ^^
,以免混淆那些以^
面值的旧C buff,即xor。
**)我不知道,为什么模式显示。可能有一个很好的解释,但就目前而言,我相信我的模式匹配技能。盲目。 编辑 @ nevets的答案解释了这种模式。
答案 1 :(得分:4)
这是一个非常有趣的问题。如果您尝试了一些小输入,您将对代码有一个大致的了解。
我使用了一个非常简单的代码来生成n = 10
时所有有效的对,这就是我得到的:
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
2 4
2 6
2 8
2 10
3 6
3 9
4 8
5 10
惊喜?我们可以在这里看到一个非常明显的模式:当i, j
满足j = k * i
时,其中k
是整数且j = k * i < n
,i, j
是有效对。它与原始方程完全无关,只取决于n
。
实际上这并不奇怪,自 (2^(nk) - 1) = ((2^k)^n - 1) = (a^n - 1)
,其中a = 2^k
,因此我们可以应用factoring rule ,这会{ {1}},因此可以(a^n - 1) = (a - 1)(a^(n - 1) + a^(n - 2) + .. + 1)
分解,即(a - 1)
。
现在问题变成了如何有效地计算这个数字。根据条件,我们有(2^(nk) - 1) % (2^k - 1) == 0
。我们之前知道j > i
。因此,j = k * i
必须在k
范围内。对于每个[2, n / i]
,我们i
有(n / i) - 2 + 1 = (n / i) - 1
个有效选项。因此,总有效对将为k
。
关于如何将等式转换为您给出的代码,请参阅@ MOehm的答案。
答案 2 :(得分:1)
变量i从1到nrt,nrt是n converted explicitily into an int value
的平方根。每次循环工作时,sum都会被(n / i)的结果相加。然后代码打印ans
(长类型),计算为(sum-nrt square-n的两倍)。