HDU 4028【map离散化DP】

题意:

给出一个数N,表示有N条指针,编号1~N,第i条指针旋转一圈所要用的时间是i,从中挑选一些指针,决定一天的长度,这个长度定义为这些指针两次重合的时间间隔。然后给出一个m,求出长度大于等于m的组合数,也就是选择指针的方法数。

解题思路:

很明显这个长度为挑出指针的最小公倍数。朴素的想法是用递推造出所有的最小公倍数,然后找出大于等于m的。但N最大为40,给合数也有2^N-1个,数组开不下。所幸这些最小公倍数有很多是重复的,经打表什么的发现好像只有10万个不同的最小公倍数。

要造出1-i这些数组成的最小公倍数很简单,用个O(N*N)的递推算法。假设前面已造好了1~i个数的最小公倍数,设其集合为s[i], 则s[i + 1] = s[i] + C(s[i], i + 1), C(s, k)表示k与集合s里的数的最小公倍数集合。为了空间不溢出,这里要开map来保存这些最小公倍数。

然后就是时间优化,因为有多组数据输入,如果对每一组数据都重新求一次1~N,最后TLE的。采取的办法是一次造表,然后直接输出。设DP[i][m],表示1~i的数组成的最小公倍数为m的个数,因为m很大,所以第二维是map,有了上面的算法,转移也很明了,DP[i + 1][m] = DP[i][m] + F(DP[i], i + 1, m),F(s, k, m)表示k与集合s所产生的最小公倍数为m的个数。

View Code
 1 #include <iostream>
2 #include <cstdio>
3 #include <string>
4 #include <cstring>
5 #include <algorithm>
6 #include <vector>
7 #include <map>
8
9 using namespace std;
10
11 const int MAXN = 40 + 1;
12 map<long long, long long> DP[MAXN];
13
14 long long gcd(long long a, long long b)
15 {
16 return b == 0 ? a : gcd(b, a % b);
17 }
18
19 long long lcm(long long a, long long b)
20 {
21 return a * b / gcd(a, b);
22 }
23
24 void build()
25 {
26 long long ne;
27 DP[1][1] = 1;
28 for(int i = 2; i < MAXN; ++i)
29 {
30 DP[i] = DP[i - 1];
31 DP[i][i]++;
32 map<long long, long long>::iterator ix = DP[i - 1].begin();
33 while(ix != DP[i - 1].end())
34 {
35 ne = lcm(ix->first, i);
36 DP[i][ne] += ix->second;
37 ++ix;
38 }
39 }
40 }
41 int main()
42 {
43 freopen("in.txt","r",stdin);
44 int T;
45 int N;
46 long long M;
47 build();
48 scanf("%d", &T);
49 for(int t = 1; t <= T; ++t)
50 {
51 scanf("%d%I64d", &N, &M);
52 long long ans = 1;
53 ans <<= N;
54 map<long long, long long>::iterator ix = DP[N].begin();
55 while(ix != DP[N].end())
56 {
57 if(ix->first >= M)
58 break;
59 ans -= ix->second;
60 ++ix;
61 }
62 printf("Case #%d: ", t);
63 printf("%I64d\n", ans - 1);
64 }
65 return 0;
66 }



posted on 2011-09-26 11:33  Kenfly  阅读(642)  评论(0编辑  收藏  举报