[英]Need Explanation of this code
代碼輸出滿足條件的(i,j)對的數量
(2^j-1) % (2^i-1) == 0
在哪里
1<=i<j<=n
n是用戶輸入的數字,在該數字下將找到(i,j)對的數量。 代碼工作得很好,但這段代碼背后的邏輯很難理解。
PS: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;
}
讓我們采用蠻力的方法解決問題並打印結果*:
############################# 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 - 1
代替(n - i) / i
並將公共子標記-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
我們可以通過從右到左查看它並計算每個summand出現的頻率來獲得相同的總和:
∑(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))
這真的只是寫這筆錢的另一種方式; 你可以通過不同的方式對summands進行分組,以便它們共享相同的分母:
∑(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;
}
其中上面g
中的∑(1, n: n / i)
已經用2 * ∑(i = 1, s: n / i) - s²
代替。
*)我在這里偷了D的力量算子 ^^
,以免混淆以面值取^
的舊C buff,即xor。
**)我不知道, 為什么模式顯示。 可能有一個很好的解釋,但就目前而言,我相信我的模式匹配技能。 盲目。 編輯 @ nevets的答案有這種模式的解釋。
這是一個非常有趣的問題。 如果您嘗試了一些小輸入,您將對代碼有一個大致的了解。
當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
, 因此我們可以應用分解規則給出(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
選擇。 因此,總有效對將是sigma((n / i) - 1, 1 <= i <= n)
。
至於如何將等式轉換為您給出的代碼,請參閱@ MOehm的答案。
變量i從1到nrt工作,nrt是n的平方根, converted explicitily into an int value
。 每次循環工作時,sum都會被(n / i)的結果相加。 然后代碼打印ans
(長類型),計算為(sum-nrt square-n的兩倍)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.