ACM - ICPC World Finals 2013 D Factors
原题下载:http://icpc.baylor.edu/download/worldfinals/problems/icpc2013.pdf
题目翻译:
问题描述
一个最基本的算数法则就是大于1的整数都能用1个或多个素数相乘的形式表示出来。当然,可以安排出多种的质因子排列方案,例如:10=2*5=5*2 20=5*2*2=2*5*2=2*2*5
让我们用f(k)表示k的质因子排列方案数,如f(10)=2,f(20)=3。
给你一个正整数n,至少有一个k使得f(k)=n,我们想知道最小的k是多少。
输入格式
输入文件至多有1000组数据,每组数据单独成行上,包含一个正整数n(n<2^63)。
输出格式
对于每组数据,输出他的问题n和最小的满足f(k)=n的k(k>1),数据保证k<2^63。
样例输入
1
2
3
105
样例输出
1 2
2 6
3 12
105 720
题目大意:假如一个大于2的正整数n的所有质因子的排列方式数为p(相同质因子调换位置不算),求满足条件的最小的n。
思路分析:
这一看又是一道数学题(而且是一道相当暴力的数学题……),首先我们将问题反过来,假如给出一个数n,求它的所有质因子排列方式数p。这个问题不难解决,我们先对n进行质因子分解,得到以下这个式子(其中\(p_i\)代表第i个质数)\[n=2^{a_1}\cdot 3^{a_2}\cdot 5^{a_3}\cdots p_{i}^{a_i},\]然后我们很轻易地通过组合公式得到如下这个答案\[p=\frac{\left(a_1+a_2+a_3+\cdots+a_i\right)!}{a_1!\cdot a_2!\cdot a_3!\cdots a_i!} \qquad (*) ,\]
这样我们就有了思路:将p转换成(*)式的形式,然后求出对应的序列\(\{a_i\}\),然后将第i个质数的指数赋为\(a_i\),然后反着求出n就好了。说起来容易但是算起来费劲……那么多数字的阶乘根本无法计算,我们要想办法缩小问题的规模,设\(\{a_i\}\)的位数为m,设\(w=\sum_{k=1}^m a_k\),考虑到题目中限定的n和p都小于\(2^{63}-1\),而根据我们的定义一定有\(n\le2^w\),而且由于\(a_i>0且a_i\ge a_{i+1}\),所以必然有\(m<63,\quad w<63\),这样我们就将问题的规模限定了,但是63!还是很难直接计算,所以我们需要对(*)式进行等价变换,将它限制在63以内的组合数的运算中,我们可以得到\[\frac{\left(a_1+a_2+a_3+\cdots+a_i\right)!}{a_1!\cdot a_2!\cdot a_3!\cdots a_i!}={w \choose a_1}\cdot{w-a_1 \choose a_2}\cdot{w-a_1-a_2 \choose a_3}\cdots{a_m \choose a_m}\qquad(**)\]
至此我们又看到了一丝希望,我们可以通过枚举所有计算出来的(**)式值在\(2^{63}\)以内的序列\(\{a_i\}\)然后计算它们对应的(**)式值和n,然后按照(**)式值进行排序,然后根据给出的询问在暴搜得到的序列中二分出来答案即可
(不知不觉这道题就讲完了)
算法流程:
1.打表前63个质数(这个手动打表就可以)
2.递推计算出所有63以内的组合数的值,备用
3.暴搜序列\(a_i\),同时计算出它们对应的p和n(一道算到某一步时p或n有爆long long的嫌疑立刻抛弃之,不符合数据范围)
4.以p为关键字排序
5.读入,二分出结果
复杂度分析:
这是一个暴搜,搜出来的序列数不会超过50000个,然后……就没有然后了(这个复杂度太难估算)
参考代码:
1 //date 20130122 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 typedef long long LL; 7 8 const LL MAX = 0x7FFFFFFFFFFFFFFFLL; 9 10 int c[100][100]; 11 const int prime[100] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523}; 12 LL x; 13 14 int stat[50000][40]; 15 int a[100]; 16 int count; 17 int p = 1; 18 19 long long dp[70][70]; 20 21 void work(int n, int m) 22 { 23 if(n < m){dp[n][m] = 0; return;} 24 if(n == m || m == 0){dp[n][m] = 1; return;} 25 if(m == 1){dp[n][m] = n; return;} 26 27 if(dp[n - 1][m] == -1)work(n - 1, m); 28 if(dp[n - 1][m - 1] == -1)work(n - 1, m - 1); 29 dp[n][m] = dp[n - 1][m - 1] + dp[n - 1][m]; 30 } 31 32 33 struct pair 34 { 35 long long n, k; 36 }num[50000]; 37 38 inline bool cmp(pair A, pair B) 39 { 40 if(A.n != B.n)return A.n < B.n; 41 else return A.k < B.k; 42 } 43 44 void DFS(int k, int last, int sum) 45 { 46 if(sum > 63)return; 47 for(int i = 1; (i <= last) && (sum + i <= 63); ++i) 48 { 49 a[k] = i; 50 long long now = 1; 51 int sign = 0; 52 int cur = sum + a[k]; long long q = 1; 53 for(int j = 1; j <= k; ++j) 54 { 55 if(MAX / dp[cur][a[j]] < q){return;} 56 else{q *= dp[cur][a[j]]; cur -= a[j];} 57 for(int w = 1; w <= a[j]; ++w) 58 { 59 if(MAX / prime[j] < now)return; 60 now *= prime[j]; 61 } 62 } 63 for(int j = 1; j <= k; ++j) 64 { 65 stat[count][j] = a[j]; 66 } 67 ++count; 68 num[count].n = q; num[count].k = now; 69 DFS(k + 1, i, sum + i); 70 } 71 } 72 73 inline void hittable() 74 { 75 count = 1; 76 memset(dp, 0xFF, sizeof dp); 77 dp[1][1] = 1; 78 for(int i = 1; i <= 63; ++i)dp[i][0] = 1; 79 for(int i = 1; i <= 63; ++i) 80 work(63, i); 81 DFS(1, 64, 0); 82 } 83 84 inline LL solve(LL x) 85 { 86 int l = 2, r = p, mid; 87 while(l < r) 88 { 89 mid = (l + r) >> 1; 90 // printf("%d %d %d %lld %lld\n", l, r, mid, num[mid].n); 91 if(num[mid].n == x)return num[mid].k; 92 if(num[mid].n < x)l = mid + 1; 93 else r = mid - 1; 94 } 95 return num[l].k; 96 } 97 98 int main() 99 { 100 freopen("factors.in", "r", stdin); 101 freopen("factors.out", "w", stdout); 102 103 hittable(); 104 std::sort(num + 1, num + count + 1, cmp); 105 long long w = num[1].n; 106 for(int i = 2; i <= count; ++i) 107 { 108 while(num[i].n == w){num[i].n = MAX; ++i;} 109 ++p; w = num[i].n; 110 } 111 std::sort(num + 1, num + count + 1, cmp); 112 113 while(scanf("%I64d", &x) != EOF) 114 { 115 LL w = solve(x); 116 printf("%I64d %I64d\n", x, w); 117 } 118 return 0; 119 }
需要注意的问题:
1.打表要用机器打表,否则代码长度会太大……
2.判断是否会爆的时候不能直接比较(那样就已经乘爆了),应该把它转化成除法比较