題目描述:一個{1, ..., n}的子集S被稱為JZP集,當且僅當對於任意S中的兩個數x,y,若(x+y)/2為整數,那么(x+y)/2也屬於S。例如,n=3,S={1,3}不是JZP集,因為(1+3)/2=2不屬於S。但是{1,2,3}的其他子集都屬於S,所以n=3時有7個JZP集給定n,求JZP集的個數。
輸入:第一行為T,表示輸入數據組數。每組數據包含一行整數n。限制條件:1<=T<=10^5,1<=n<=10^7
輸出:對第i組數據,輸出Case #i:然后輸出JZP集的個數。
這道題目考的知識主要是素數篩選,直接對每個n,求對應的JZP集肯定是會超時的(開始我也是如此,以為很簡單的一道題,沒想到一提交就是超時),后來用遞推的思路改進下才pass。下面分享下我的答題思路:
首先,先從簡單的情況分析:
n = 1時,JZP1 = {{},{1}} ,個數為2;
n = 2時,JZP2 = {{}, {1}, {2}, {1,2}},個數為4
n = 3時,JZP3 = {{}, {1}, {2}, {1,2}, {3}, {2,3}, {1,2,3}},個數為7
n = 4時,JZP4 = {{}, {1}, {2}, {1,2}, {3}, {2,3}, {1,2,3}, {4}, {3,4}, {1,4}, {2,3,4},{1,2,3,4}},個數為12
n = 5時,JZP5 = ...
從上面的JZP集合來看,一個{1...n}的集合中滿足JZP的集合有:空集{},只有一個元素的集合{k}(1 <= k <= n),包含兩個或兩個以上元素的集合{a1,a2,a3...ak}(2 <= ak <= n,ak - ak-1 = 2 * x + 1, 0 <= x <= (n - 2)/ 2),也就是說每個滿足條件的集合中的元素是等差數列,數列的差值可以取值為1,3,5,7,9...2*x+1(0 <= x <= (n - 2)/ 2)。
根據上面的思路,對每個差值1,3,5,7,9...2*x+1(0 <= x <= (n - 2)/ 2),在找出{1...n}的集合中找出滿足這個差值的最大等差數列,即{1,2,3,4...n}(差值為1);{1,4,7,10...},{2,5,8,11...},{3,6,9,12...}(差值為3);{1,6,11,16...},{2,7,12,17...},{3,8,13,18},{4,9,14,19..},{5,10,15,20...}(差值為5)...。然后再這些集合中選出2個或2個以上的相鄰元素作為子集合就是滿足題目的JZP集。所以一個{1...n}的集合中JZP集合的個數為:1 + n + {n * (n - 1) / 2} + {(n % 3) * (n / 3) * ( n / 3 + 1) / 2 + (3 - n % 3) * (n / 3) * ( n / 3 - 1) / 2} + {(n % 5) * (n / 5) * ( n / 5 + 1) / 2 + (5 - n % 5) * (n / 5) * ( n / 5 - 1) / 2} + ...。
所以對於一個給定的n,我們可以直接求出JZP集的個數,時間復雜度為O(n),再看題目有T組數據,所以總的復雜度是O(n * T),而1<=T<=10^5,1<=n<=10^7,顯然復雜度過大,容易想到的方法是提前求出n為1~10^7所對應的JZP集個數,並存在數組中,后面對每組數據,直接查數組就可以了。現在關鍵是怎么求n為1~10^7所對應的JZP集個數,如果還是按前面的方法對每個數n都直接求對應的JZP集的個數,復雜度為O(n^2),顯然這是會超時的。可不可以利用前面n-1對應的JZP集的個數求n對應的JZP集的個數呢?如果可以這樣就減少了重復計算的次數,假設dp[n - 1]表示n - 1對應的JZP集的個數,現在分析dp[n]與dp[n - 1]的關系,n相對於n-1增加的JZP集有{n},差值為1的數列中增加的JZP集為n - 1,差值為3的數列中增加的JZP集為 (n - 1) / 3,...,所以dp[n] = dp[n - 1] + 1 + (n - 1) + (n - 1) / 3 + (n - 1) / 5 + ... + 1,關鍵是求(n - 1) + (n - 1) / 3 + (n - 1) / 5 + ... + 1了,假設temp[n] = (n - 1) + (n - 1) / 3 + (n - 1) / 5 + ... + 1,那么temp[n] = temp[n - 1] + cn(n - 1),其中cn(n - 1)是整除n - 1的所有奇數的個數,現在問題簡化為求一個數能被多少個奇數整除,用暴力肯定不行,想想這個和素數的思路差不多,素數篩選的方法能用O(nlogn)的復雜度求出1~n中的每個數被奇數整除的個數,也就是整個算法的復雜度是O(nlogn),這個時間復雜度應該夠了。
有了上面的分析,實現代碼就簡單了,具體代碼如下:
1 #include <iostream> 2 using namespace std; 3 4 long long dp[10000001]; 5 long long cn[10000001]; 6 int main() 7 { 8 int t, n; 9 long long ans, cnt; 10 11 for (int j = 1; j <= 10000000; j += 2) 12 { 13 for (int i = j; i <= 10000000; i += j) 14 cn[i]++; 15 } 16 dp[1] = 2; 17 cnt = 0; 18 for (int j = 2; j <= 10000000; j++) 19 { 20 cnt += cn[j - 1]; 21 dp[j] = dp[j - 1] + cnt + 1; 22 } 23 cin >> t; 24 for (int i = 0; i < t; i++) 25 { 26 cin >> n; 27 cout << "Case #" << i + 1 << ":" << endl << dp[n] << endl; 28 } 29 return 0; 30 }
