4.满足条件的m的个数 给出一个数字n和k,请找出有多少个不同的整数m满足: 1) m<2^n 2) m>=0 3) |f(m,n)|<=k 其中f(x,n)的定义如下: f(0,n)=n; f(x,n)=f([x/2],n),x为偶数 f(x,n)=f([x/2],n)-2,x为奇数 Input 每组一行,分别给定n,k(1<=n<=62,2<=k<=5) Output 每组一行,给出满足条件的m的个数 Sample Input 1 2 4 3 Sample Output 2 14 主函数: #include <iostream> #include <time.h> using namespace std; int Fun(int n, int k); int main() { time_t startTime; time_t endTime; time(&startTime); for (int i=1; i<=20; i++) for (int j=2; j<=5; j++) cout << i << "," << j << ": " << Fun(i, j) << "\t"; cout << endl; time(&endTime); cout << difftime(endTime, startTime) << endl; getchar(); return 0; } 方法一:最直接的算法,根据题意递归求解。 int f(int x, int n) { if (x == 0) return n; if ((x&1) == 0) //x为偶数 return f(x/2, n); else //x为奇数 return f(x/2, n) - 2; } int Fun(int n, int k) { long long max = 1<<n; long long s = 0; for (int m=0; m<max; m++) { if (abs(f(m, n)) <= k) ++s; } return s; } 方法二:以空间换时间,把所有的f(m, n)值存储到一个数组中,然后查表;由于max的值太大了,不可能建立这么大的数组, 但是可以建立一个较小的数组(长度为400000), 存储对应的f(m, n)值,当m较小时,可以直接查表; 当m >= M时,递归减小m的值,直到m < M ,然后查表。 void Table(int a[], int max, int n)//建立一个表,把所有的f(x, n)值存储到一个数组中 { a[0] = n; a[1] = n - 2; for (int i=2; i<max; i++) { if ((i&1) == 0) a[i] = a[i/2]; else a[i] = a[i/2] - 2; } } long long Fun(int n, int k) { const int M = 400000; int a[M]; Table(a, M, n);//建表 long long max = 1<<n; //2^n long long s = 0; if (max < M)//当m较小时,可以直接查表 { for (int m=0; m<max; m++) { if (abs(a[m]) <= k) ++s; } } else//当m >= M时,递归减小m的值,直到m < M ,然后查表 { for (int m=0; m<M; m++)//先计算前面小于M的部分 { if (abs(a[m]) <= k) ++s; } long long t; int f; for (long long m=M; m<max; m++)//计算大于M的部分 { f = 0; t = m; while (t >= M)//递归减小m的值,累积奇数的个数,如果是奇数要减去2 { if ((t&1) == 1) ++f; t >>= 1; } f = a[t] - f * 2;//查表 if (abs(f) <= k) ++s; } } return s; } 补充一个算法: 这是http://www.programfan.com/club/的sarrow得到的算法。 由 | n; x == 0 f(x, n) = | f([x / 2], n); x even number | f([x / 2], n) - 2; x odd number 观察x的二进制数,用ones_in_x表示x的二进制数中'1'的个数,zero_in_x表示x的二进制数中'0'的个数, 可以得到 f(x, n) = n - 2 * ones_in_x = n - 2 * (n - zero_in_x) = 2 * zero_in_x - n 所以|f(m,n)|<=k 就是 | 2 * zero_in_m - n | <= k 即 -k <= 2 * zero_in_m - n <= k 也即 (n - k) / 2 <= zero_in_m <= (n + k) / 2 又 0 <= m < 2^n 即满足条件的m,其二进制数中'0'的个数应该大于等于i,小于等于j,其中 i = max{(n - k) / 2, 0};对(n - k) / 2应该四舍五入 j = max{(n + k) / 2, n}; 所以求满足条件的m的个数,相当于解一个排列组合题目:分别求从n个'0'取出k个'0'的取法, 其中(i<=k<<j),再求所有取法之和, 即 set_of_m = c(i, n) + ... + c(j, n); int Gcd(int m, int n)//求最大公约数 { int temp; while (n > 0) { temp = m % n; m = n; n = temp; } return m; } long long Combination(int m, int n)//求组合C(m, n),注意不能直接按定义求,否则会溢出,先约分再除 { int *pm = new int[m]; int *pn = new int[m]; for (int i=0; i<m; ++i) pm[i] = i + 1; for (int i=0; i<m; ++i) pn[i] = n - i; int g; for (int i=0; i<m; ++i) for (int j=0; j<m; ++j) { g = Gcd(pm[i], pn[j]);//求最大公约数 pm[i] /= g; pn[j] /= g; } long long numerator = 1; long long denominator = 1; for (int i=0; i<m; ++i) numerator *= pn[i]; for (int i=0; i<m; ++i) denominator *= pm[i]; return numerator / denominator; } long long Fun(int n, int k) { int low = (n < k) ? 0 : ((((n-k)&1) == 0) ? (n-k)/2 : (n-k)/2 + 1); int high = (n < k) ? n : (n+k)/2; long long s = 0; for (int i=low; i<=high; ++i) s += Combination(i, n); return s; }

评论