2014年百度之星程序设计大赛 - 初赛(第二轮)JZP Set

  题目描述:一个{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 }

你可能感兴趣的:(程序设计)