数论一
Posted 萌萌的美男子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数论一相关的知识,希望对你有一定的参考价值。
1、UVa 10916 Factstone Benchmark(对数函数的利用--乘化加)
题意:给出年份,每个10年对应一个当前计算机可支持的字节位数,计算n! < max(max 为当前计算机能表示的最大整数),求最大n.
思路:字节数k = (year - 1940) / 10, 问题就转化成 n ! < 2 ^ k < (n + 1) !, 对两边同取对数,因为log(a*b) = log(a) + log(b);所以log(n!) = sum(log(i)), ( 1<= i <= n), 只要找到最小的sum(log(i)) > k * log(2) ,答案就是i- 1.
1 #include<iostream> 2 #include<cmath> 3 using namespace std; 4 int main() 5 { 6 int y; 7 while (~scanf("%d", &y)&&y) 8 { 9 int n = (y - 1940) / 10; 10 double re = pow(2.0,1.0*n)*log(2.0); 11 double sum = 0; 12 for(int i=1;;i++) 13 { 14 sum += log(1.0*i); 15 if (sum > re) 16 { 17 printf("%d\n", i - 1); 18 break; 19 } 20 } 21 } 22 return 0; 23 }
2、zoj 1239 Hanoi Tower Troubles Again!
题意:给出n个柱子,从第一个柱子开始放珠子,珠子编号从1开始,要求每个柱子上相邻两个珠子的和是平方数。
思路:
h(1) = 1:1个柱子时,放上1之后,2就放不上去了,不满足柱子上相邻的球之和为平方数(1 + 2 = 3,不是平方数);
h(2) = 3:2个柱子时,1和2各放在1个柱子上,3可以放在1上面(1 + 3 = 4 = 2 ^ 2);
h(i) = h(i - 1) + i + 1:i为奇数时,在i - 1柱子的基础上增加了一个柱子,这时候可以再放i + 1个球;
h(i) = h(i - 1) + i:i为偶数时,在i - 1柱子的基础上增加了一个柱子,这时候可以再放i个球。
f(n) = f(n - 1) + (n + 1) / 2 + (n + 1) / 2 (n > 2)
1 //h(1) = 1:1个柱子时,放上1之后,2就放不上去了,不满足柱子上相邻的球之和为平方数(1 + 2 = 3,不是平方数); 2 //h(2) = 3:2个柱子时,1和2各放在1个柱子上,3可以放在1上面(1 + 3 = 4 = 2 ^ 2); 3 //h(i) = h(i - 1) + i + 1:i为奇数时,在i - 1柱子的基础上增加了一个柱子,这时候可以再放i + 1个球; 4 //h(i) = h(i - 1) + i:i为偶数时,在i - 1柱子的基础上增加了一个柱子,这时候可以再放i个球。 5 // f(n) = f(n - 1) + (n + 1) / 2 + (n + 1) / 2 (n > 2) 6 #include<iostream> 7 using namespace std; 8 int re[55]; 9 void Init() 10 { 11 re[1] = 1, re[2] = 3; 12 for (int i = 3; i <= 50; i++) re[i] = re[i - 1] + (i + 1) / 2 + (i + 1) / 2; 13 } 14 int main() 15 { 16 Init(); 17 int t; 18 scanf("%d", &t); 19 while (t--) 20 { 21 int n; 22 scanf("%d", &n); 23 printf("%d\n", re[n]); 24 } 25 return 0; 26 }
3、hdu1465 不容易系列之一(错排)
题意:求所有错排的错排方案数。
思路:
错排数公式:f[n] = (n - 1) * (f[n - 1] + f[n - 2]);
也可以这么想;
(1).f[1] = 0; f[2] = 1;
(2).如果确定f[n - 1] 和 f[n - 2] 的话。
f[n] 中必然包含 f[n - 1] * (n - 1)种情况。 即把新加入的一封和之前的任一一封交换,所得到的必然是错排。
1 //错排数公式:f[n] = (n - 1) * (f[n - 1] + f[n - 2]); 2 //也可以这么想; 3 //(1).f[1] = 0; f[2] = 1; 4 //(2).如果确定f[n - 1] 和 f[n - 2] 的话。 5 //f[n] 中必然包含 f[n - 1] * (n - 1)种情况。 即把新加入的一封和之前的任一一封交换,所得到的必然是错排。 6 //f[n] 中另一部分就是f[n - 2] * (n - 1) 即之前的 n - 1 封中有一封没有错排,把这封与新加入的一封交换进行错排。 7 #include<iostream> 8 using namespace std; 9 long long re[25]; 10 void Init() 11 { 12 re[1] = 0, re[2] = 1; 13 for (int i = 3; i <= 20; i++) re[i] = (re[i - 1] + re[i - 2])*(i - 1); 14 } 15 int main() 16 { 17 Init(); 18 int n; 19 while (~scanf("%d", &n)) 20 { 21 printf("%lld\n",re[n]); 22 } 23 return 0; 24 }
4、HDU 3625 Examining the Rooms(第一类斯特林数)
题意:有n个房间,n!个钥匙,在房间中,最多可以破k扇门,然后得到其中的钥匙,去开其它的门,但是第一扇门不可以破开,求可以打开所有门的概率。
思路:
求N个房间形成1~K个环有多少种可能,然后除以总的分配方案数即为题目要我们求的概率。
第一类斯特林数S(N, K) = S(N - 1, K - 1) + (N - 1)*S(N - 1, k)恰恰就是求N个元素形成K个非空循环排列的方法数。
S(N,M)-S(N-1,M-1),表示N个元素形成M个环,减去1独自成环,即剩下的N-1个元素形成M-1个环,算得的结果便是所求值。
1 //求N个房间形成1~K个环有多少种可能,然后除以总的分配方案数即为题目要我们求的概率 2 //第一类斯特林数S(N, K) = S(N - 1, K - 1) + (N - 1)*S(N - 1, k)恰恰就是求N个元素形成K个非空循环排列的方法数 3 //S(N,M)-S(N-1,M-1),表示N个元素形成M个环,减去1独自成环,即剩下的N-1个元素形成M-1个环,算得的结果便是所求值 4 #include<iostream> 5 using namespace std; 6 long long stl[25][25], cal[25]; 7 void Init() 8 { 9 cal[0] = cal[1] = 1; 10 for (int i = 2; i <= 20; i++) cal[i] = cal[i - 1] * i; 11 for (int i = 1; i <= 20; i++) 12 { 13 stl[i][i] = 1; 14 for (int j = 1; j < i; j++) 15 { 16 stl[i][j] = stl[i - 1][j - 1] + (i - 1)*stl[i - 1][j]; 17 } 18 } 19 } 20 int main() 21 { 22 int t; 23 scanf("%d", &t); 24 Init(); 25 while (t--) 26 { 27 int n, k; 28 scanf("%d%d", &n, &k); 29 long long ans = 0; 30 for (int i = 1; i <= k; i++) 31 { 32 ans += stl[n][i] - stl[n - 1][i - 1]; 33 } 34 printf("%.4lf\n", 1.0*ans / cal[n]); 35 } 36 return 0; 37 }
5、hdu 4045 Machine scheduling(组合+第二类斯特林数)
题意:有N个机器,每天选出R个机器,而且每两个机器的编号差要大于等于K,而且每天将R个机器最多分为M组工作,问最多有多少种方案 。
思路:
首先是从N个机器中选出R个满足条件的机器有多少种。然后将R个机器最多分为M组有多少种,答案为二者的乘积。
对于后者:
为第二类Strling数的和 : S[n][1] + S[n][2] + ... + S[n][m]。
S(p,k)的递推公式是:S(p,k)=k*S(p-1,k)+S(p-1,k-1) ,1<= k<=p-1
边界条件:S(p,p)=1 ,p>=0 ;S(p,0)=0 ,p>=1。
对于前者:
1:如果是把n个求放入k个盒子中(每个盒子必须要有球),那么由插板法得 方案数为 C(n-1,k-1);
2:如果是把n个求放入k个盒子中(盒子可以为空),那么由插板法得 方案数为 C(n + k - 1, k - 1);
要从n个元素编号1~n中选出r个元素来,使得任意两个元素编号相差>=k
解法:先按照 1 k + 1 2 * k + 1 .... (r - 1)*k + 1 也就是刚好 间隔 k个排列下来
那么 总共n个元素,剩余 n - (r - 1)*k - 1个元素可以看成空格,极为space, 将它们插入这r个元素的r + 1个空档中,
这样就能保证枚举了素有的方法数,切满足任意两个元素编号相差 >= k的要求
所以这个问题简化为:将space个元素分配到r + 1个区域中,可以为空(每两个机器之前至少有K个间隔,那么如果还剩余一些位置,则把这些多余的插入到R个机器中。那么剩余位置便是N - ((R - 1)*K + 1), 对于R个机器,R + 1个位置,接下来便是把N - ((R - 1)*K + 1)分为R + 1个集合,而且可以为空。做法是添加R + 1个物品,然后用插板法,这样保证 每一个集合都至少有一个,然后再把每一个集合都减掉一个便是结果,最终结果便是C[N - ((R - 1)*K + 1) + R + 1 - 1][R]。)
1 //有N个机器,每天选出R个机器,而且每两个机器的编号差要大于等于K,而且每天将R个机器最多分为M组工作,问最多有多少种方案 2 //1:如果是把n个求放入k个盒子中(每个盒子必须要有球),那么由插板法得 方案数为 C(n-1,k-1); 3 //2:如果是把n个求放入k个盒子中(盒子可以为空),那么由插板法得 方案数为 C(n + k - 1, k - 1); 4 //要从n个元素编号1~n中选出r个元素来,使得任意两个元素编号相差>=k 5 //解法:先按照 1 k + 1 2 * k + 1 .... (r - 1)*k + 1 也就是刚好 间隔 k个排列下来 6 //那么 总共n个元素,剩余 n - (r - 1)*k - 1个元素可以看成空格,极为space, 将它们插入这r个元素的r + 1个空档中, 7 //这样就能保证枚举了素有的方法数,切满足任意两个元素编号相差 >= k的要求 8 //所以这个问题简化为:将space个元素分配到r + 1个区域中,可以为空(每两个机器之前至少有K个间隔,那么如果还剩余一些位置,则把这些多余的插入到R个机器中。那么剩余位置便是N - ((R - 1)*K + 1), 对于R个机器,R + 1个位置,接下来便是把N - ((R - 1)*K + 1)分为R + 1个集合,而且可以为空。做法是添加R + 1个物品,然后用插板法,这样保证 每一个集合都至少有一个,然后再把每一个集合都减掉一个便是结果,最终结果便是C[N - ((R - 1)*K + 1) + R + 1 - 1][R]。) 9 #include<iostream> 10 using namespace std; 11 const int maxn = 1010; 12 const int MOD = 1e9 + 7; 13 long long C[maxn][maxn]; 14 long long stri2[maxn][maxn];//第二类斯特林数 15 void Init() 16 { 17 memset(C, 0, sizeof(C)); 18 for (int i = 0; i < maxn; i++) 19 { 20 C[i][0] = 1; 21 for (int j = 1; j <= i; j++) 22 { 23 C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD; 24 } 25 } 26 for (int i = 1; i < maxn; i++) 27 { 28 stri2[i][0] = 0; 29 stri2[i][i]=stri2[i][1] = 1; 30 for (int j = 1; j < i; j++) 31 { 32 stri2[i][j] = (stri2[i - 1][j] * j + stri2[i - 1][j - 1]) % MOD; 33 } 34 } 35 } 36 int main() 37 { 38 Init(); 39 int n, r, k, m; 40 while (~scanf("%d%d%d%d", &n, &r, &k, &m)) 41 { 42 int res = n - (r - 1)*k - 1; 43 if (res < 0) 44 { 45 printf("0\n"); 46 continue; 47 } 48 long long ans = 0; 49 for (int i = 0; i <= m; i++) ans = (ans + stri2[r][i]) % MOD; 50 ans = (ans*C[res + r][r]) % MOD; 51 printf("%lld\n", ans); 52 } 53 return 0; 54 }
6、HDU 1023 Train Problem II(卡特兰数+模拟大数乘除<高精度递推>)
题意:给你一个数n,表示有n辆火车,编号从1到n,从远方驶过来,问你有多少种出站的可能。
思路:
1.Catalan数的组合公式为 Cn=C(2n,n) / (n+1);
2.此数的递归公式为 h(n ) = h(n-1)*(4*n-2) / (n+1)。
http://blog.163.com/lz_666888/blog/static/1147857262009914112922803/
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int cat[110][110];//每一位存放大数的4位 6 //Catalan数的组合公式为 Cn=C(2n,n) / (n+1); 7 //递推公式为 h(n) = h(n - 1)*(4 * n - 2) / (n + 1); 8 int length[110];//记录每一位卡特兰数的位数 9 const int MOD = 10000; 10 void Init() 11 {//求出1~100位卡特兰数 12 memset(cat, 0, sizeof(cat)); 13 cat[1][0] = 1; 14 int len = 1; 15 length[1] = 1; 16 for (int i = 2; i <= 100; i++) 17 { 18 //乘法 19 int r = 0; 20 for (int j = 0; j < len; j++) 21 { 22 int temp = cat[i - 1][j] * (4 * i - 2); 23 cat[i][j] = (temp+r)%MOD; 24 r = (temp + r) / MOD; 25 } 26 //进位 27 while (r) 28 { 29 cat[i][len++] = r %MOD; 30 r /= MOD; 31 } 32 //除法 33 r = 0; 34 for (int j = len - 1; j >= 0; --j) 35 { 36 int temp = r * MOD + cat[i][j]; 37 cat[i][j] = temp / (i + 1); 38 r = temp % (i + 1); 39 } 40 //除法结束后高位0处理 41 while (cat[i][len - 1] == 0) 42 { 43 len--; 44 } 45 length[i] = len; 46 } 47 } 48 int main() 49 { 50 Init(); 51 int n; 52 while (~scanf("%d", &n)) 53 { 54 int len = length[n]; 55 printf("%d", cat[n][len - 1]); 56 for (int j = len - 2; j >= 0; j--) printf("%.4d", cat[n][j]); 57 printf("\n"); 58 } 59 return 0; 60 }
7、HDU 1297 Children’s Queue(模拟大数加法<高精度递推>)
题意:F代表女孩,M代表男孩,女孩不能单独出现但是可以不出现,即不能出现MFM这种情况,给定一个数n,问有多少种站队方式。
思路:
一个长度n的队列可以看成一个n - 1的队列再追加的1个小孩,这个小孩只可能是:
a.男孩,任何n - 1的合法队列追加1个男孩必然是合法的,情况数为f[n - 1];
b.女孩,在前n - 1的以女孩为末尾的队列后追加1位女孩也是合法的,我们可以转化为n - 2的队列中追加2位女孩;
一种情况是在n - 2的合法队列中追加2位女孩,情况数为f[n - 2];
但我们注意到本题的难点,可能前n - 2位以女孩为末尾的不合法队列(即单纯以1位女孩结尾),也可以追加2位女孩成为合法队列,而这种n - 2不合法队列必然是由n - 4合法队列 + 1男孩 + 1女孩的结构,即情况数为f[n - 4]。
f[n] = f[n - 1] + f[n - 2] + f[n - 4]。
1 //一个长度n的队列可以看成一个n - 1的队列再追加的1个小孩,这个小孩只可能是: 2 //a.男孩,任何n - 1的合法队列追加1个男孩必然是合法的,情况数为f[n - 1]; 3 //b.女孩,在前n - 1的以女孩为末尾的队列后追加1位女孩也是合法的,我们可以转化为n - 2的队列中追加2位女孩; 4 //一种情况是在n - 2的合法队列中追加2位女孩,情况数为f[n - 2]; 5 //但我们注意到本题的难点,可能前n - 2位以女孩为末尾的不合法队列(即单纯以1位女孩结尾),也可以追加2位女孩成为合法队列,而这种n - 2不合法队列必然是由n - 4合法队列 + 1男孩 + 1女孩的结构,即情况数为f[n - 4]。 6 //f[n] = f[n - 1] + f[n - 2] + f[n - 4] 7 #include<iostream> 8 #include<cstring> 9 using namespace std; 10 int f[1010][1010]; 11 int length[1010]; 12 void Init() 13 { 14 memset(f, 0, sizeof(f)); 15 f[1][0] = 1; 16 f[2][0] = 2; 17 f[3][0] = 4; 18 f[4][0] = 7; 19 int len = 1; 20 length[1] = length[2] = length[3] = length[4] = 1; 21 for (int i = 5; i <= 1000; i++) 22 { 23 for (int j = 0; j < len; j++) 24 { 25 f[i][j] += f[i - 1][j] + f[i - 2][j] + f[i - 4][j]; 26 f[i][j + 1] += f[i][j] / 10000; 27 f[i][j] %= 10000; 28 } 29 while (f[i][len]) len++; 30 length[i] = len; 31 } 32 } 33 int main() 34 { 35 Init(); 36 int n; 37 while (~scanf("%d", &n)) 38 { 39 int len = length[n]; 40 printf("%d", f[n][len - 1]); 41 for (int i = len - 2; i >= 0; i--) printf("%04d", f[n][i]); 42 printf("\n"); 43 } 44 return 0; 45 }
8、hdu 1466 计算直线交点数
题意:平面上有n条直线,且无三线共点,问这些直线能有多少种不同交点数。比如,如果n=2,则可能的交点数量为0(平行)或者1(不平行)。
思路:
分析加入第N条直线的情况(这里以N=4为例):
(分类方法:和第N条直线平行的在a组,其余在b组)
1、 第四条与其余直线全部平行 :
0+4*0+0=0;
2、 第四条与其中两条平行:
交点数为0+(n-1)*1+0=3;
3、 第四条与其中一条平行:
这两条平行直线和另外两点直线的交点数为(n-2)*2=4,
而另外两条直线既可能平行也可能相交,
因此可能交点数为: 0+(n-2)* 2+0=4 或者 0+(n-2)*2+1=5
4、 第四条直线不与任何一条直线平行,交点数为:0+(n-3)*3+0=3 或0+ (n-3)*3+2=5 或0+ (n-3)*3+3=6
即n=4时,有0个,3个,4个,5个,6个不同交点数。
所以从上述n=4的分析过程中,我们发现:
m条直线的交点方案数 =(m - r)条平行线与r条直线交叉的交点数 + r条直线本身的交点方案 = (m - r )* r + r 条之间本身的交点方案数(0 < = r < m )
将 n 条直线排成一个序列,直线 2 和直线 1 最多只有一个交点,直线 3 和直线 1 和直线 2 最多有两个交点......直线n和其他 n - 1 条直线最多有 n - 1 个交点,由此得出 n 条直线互不平行且无三线共点的最多交点数:max = 1 + 2 + . . . + (n - 1) = n * (n - 1) / 2。
其中每个自由直线与每个平行直线都有一个交点,j自由直线与i平行直线的交点数为 j * i,所以n条直线的交点数等于自由直线与平行直线的交点加上自由直线内部形成的交点。
————————以上摘自:http://blog.csdn.net/wang907553141/article/details/52165629
1 #include<iostream> 2 using namespace std; 3 const int maxn = 210; 4 bool dp[25][maxn];//dp[i][j]表示i条直线相交时是否有j个交点 5 void Init() 6 { 7 for (int i = 0; i <= 20; i++) dp[i][0] = true;//所有的直线都平行 8 for (int i = 2; i <= 20; i++) 9 {//i条直线 10 for (int j = 1; j < i; j++) 11 {//枚举和第i条边相交的边的数目 12 for (int k = 0; k < maxn; k++) 13 {//枚举j条边的交点情况 14 if (dp[j][k]) dp[i][(i - j)*j + k] = true; 15 } 16 17 } 18 } 19 } 20 21 int main() 22 { 23 Init(); 24 int n; 25 while (~scanf("%d", &n)) 26 { 27 for (int i = 0; i < maxn; i++) 28 { 29 if (dp[n][i]) 30 { 31 if (i > 0) printf(" "); 32 printf("%d", i); 33 } 34 } 35 printf("\n"); 36 } 37 return 0; 38 }
以上是关于数论一的主要内容,如果未能解决你的问题,请参考以下文章
最新开源指数发布:VSCode成为第一代码编辑器百度Apollo增长最快