1. 麦森数
来源:NOIP2003提高组 https://ac.nowcoder.com/acm/contest/231/D
算法知识点: 高精度,快速幂,数学
复杂度:
解题思路:
首先考虑如何求位数。
我们发现在 之间的数均有 位。因此对于任意正整数 ,它的位数是 。
而由于2的整次幂的末位数字不为0,因此 的位数和 的位数相同,所以 的位数是 。
cmath
库中有函数 log10()
,直接使用即可。
然后考虑如何求最后500位数。
- 直接乘 次,时间复杂度是
- 用快速幂,时间复杂度是
因此我们选择第二种方法。
C++ 代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int N = 510; int a[N], c[N], res[N]; void mul(int a[N], int b[N], int res[N]) { memset(c, 0, N * 4); for (int i = 0; i < 500; i ++ ) for (int j = 0; j < 500; j ++ ) if (i + j < 500) c[i + j] += a[i] * b[j]; for (int i = 0, t = 0; i < 500; i ++ ) { t += c[i]; res[i] = t % 10; t /= 10; } } void qmi(int p) { res[0] = 1; a[0] = 2; while (p) { if (p & 1) mul(a, res, res); mul(a, a, a); p >>= 1; } res[0] -- ; } int main() { int p; scanf("%d", &p); printf("%d\n", int(log10(2) * p) + 1); qmi(p); for (int i = 499, j = 0; j < 10; j ++ ) { for (int k = 0; k < 50; k ++, i -- ) printf("%d", res[i]); puts(""); } return 0; }
2. 循环
来源:NOIP2005提高组 https://ac.nowcoder.com/acm/contest/233/D
算法知识点: 高精度,数学,数论,递推
复杂度:
解题思路:
引理1: 前 位的所有循环节长度,一定是 前 位循环节长度的子集。
证明:
- 如果前 位都不相同,那么前 位一定也不相同。
引理2: 假设最小循环节长度是 ,那么所有循环节长度一定都是 的整数倍。
证明:
- 假设有循环节长度 不是 的倍数,那么 也一定是循环节,且 ,和 是最小循环节长度矛盾。
因此我们可以从小到大一位一位递推出前 位的循环节长度。
假设已经求出前 位的循环节长度 ,那当我们求前 位的循环节长度时,
只需枚举 是否和 相同即可。
注意这里只需要枚举前10项,因为前 位是固定不变的,只有第 位会变化,因此最多只有10种不同选择。
C++ 代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 110; int n, m; int nums[N], power[N], prod[N], npower[N]; void mul(int res[], int a[], int b[]) { static int c[N]; memset(c, 0, sizeof c); for (int i = 0; i < m; i ++ ) for (int j = 0; j < m; j ++ ) c[i + j] += a[i] * b[j]; for (int i = 0, t = 0; i < m; i ++ ) { t += c[i]; res[i] = t % 10; t /= 10; } } bool check_eq(int a[], int b[], int k) { for (int i = 0; i < k; i ++ ) if (a[i] != b[i]) return false; return true; } int main() { string number; cin >> number >> m; n = number.size(); for (int i = 0, j = n - 1; i < min(n, m); i ++, j -- ) nums[i] = number[j] - '0'; memcpy(power, nums, sizeof nums); int per[N] = {1}; for (int k = 1; k <= m; k ++ ) { int r = 0; memcpy(prod, nums, sizeof nums); memset(npower, 0, sizeof npower); npower[0] = 1; while (true) { mul(prod, prod, power); mul(npower, npower, power); r ++ ; if (check_eq(prod, nums, k)) break; if (r > 10) break; } if (r > 10) { per[0] = -1; puts("-1"); break; } for (int i = 0, t = 0; i < N; i ++ ) { t += per[i] * r; per[i] = t % 10; t /= 10; } memcpy(power, npower, sizeof power); } if (per[0] != -1) { int k = N - 1; while (!per[k]) k -- ; while (k >= 0) printf("%d", per[k -- ]); } return 0; }
3. 数列
来源:NOIP2006提高组 https://ac.nowcoder.com/acm/contest/234/C
算法知识点: 数学,二进制,位运算
复杂度:
解题思路:
引理:当 时,。
证明:对 由数学归纳法可证。
- 假设当 时是正确的,那么当 时:
接下来将序列中的每个数,唯一映射到一个二进制数:
- 如果第 个方幂存在,则二进制数的第 位为1,否则为0。
由上述引理,序列中任意两数的大小关系,和映射成二进制数后的大小关系相同。
因此我们可以先求出第 个二进制数,然后再反射出原数即可。
C++ 代码:
#include <iostream> #include <algorithm> using namespace std; typedef long long LL; LL get(int a, int b) { LL res = 1; while (b -- ) res *= a; return res; } int main() { int n, k; cin >> k >> n; LL res = 0; for (int i = 0; i < 20; i ++ ) if (n >> i & 1) res += get(k, i); cout << res << endl; return 0; }
4. 魔法阵
来源:NOIP2016提高组 https://ac.nowcoder.com/acm/contest/244/A
算法知识点: 前缀和,数学,组合计数,乘法原理,加法原理
复杂度:
解题思路:
一个魔法阵如下所示:
A和B之间的距离是2t,B和C之间的距离是6t+k,C和D之间的距离是t,其中t, k均为正整数。
左边红色部分框出的A和B是绑定的,右边绿色部分框出的C和D也是绑定的。
因此整个系统共有三个自由度:t、红色部分、绿色部分。
同时枚举三个自由度的计算量过大。在1秒内,我们只能枚举其中两个自由度。
首先枚举t。接下来并列枚举绿色部分和红色部分:
- 从左到右枚举绿色部分,当绿色部分固定后,则C应该累加的次数是所有满足要求的A和B的
cnt[A] * cnt[B]
的和,再乘以cnt[D]
。其中cnt[A], cnt[B], cnt[D]
是A, B, D
出现的次数。所有满足要求的A和B就是整个线段左边的某个前缀,因此可以利用前缀和算法来加速计算。cnt[D]
同理可得。 - 从右到左枚举红色部分可做类似处理。
参考文献
C++ 代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 15010, M = 40010; int n, m; int cnt[N]; int a[N], b[N], c[N], d[N], x[M]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i ++ ) { scanf("%d", &x[i]); cnt[x[i]] ++ ; } for (int t = 1; t * 9 + 2 <= n; t ++ ) { int sum = 0; for (int D = t * 9 + 2; D <= n; D ++ ) { int A = D - t * 9 - 1; int B = A + t * 2; int C = D - t; sum += cnt[A] * cnt[B]; c[C] += sum * cnt[D]; d[D] += sum * cnt[C]; } sum = 0; for (int A = n - t * 9 - 1; A; A -- ) { int B = A + t * 2; int D = A + t * 9 + 1; int C = D - t; sum += cnt[C] * cnt[D]; a[A] += sum * cnt[B]; b[B] += sum * cnt[A]; } } for (int i = 1; i <= m; i ++ ) printf("%d %d %d %d\n", a[x[i]], b[x[i]], c[x[i]], d[x[i]]); return 0; }
另外,牛客暑期NOIP真题班限时免费报名
报名链接:https://www.nowcoder.com/courses/cover/live/248
报名优惠券:DCYxdCJ