专栏文章
P1050题解
P1050题解参与者 5已保存评论 5
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 5 条
- 当前快照
- 1 份
- 快照标识符
- @miqhjnb5
- 此快照首次捕获于
- 2025/12/04 04:54 3 个月前
- 此快照最后确认于
- 2025/12/04 04:54 3 个月前
提前说的话
本文中:
所有 符号()表示 整除 (即:存在一个 ,使 );
所有提到“位”(进制位)的地方都指十进制下的进制位。而位的编号从右往左依次 、、、……,后 位表示编号为 ~ 的进制位,第 位表示编号为 的进制位;
所有 符号()表示 整除 (即:存在一个 ,使 );
所有提到“位”(进制位)的地方都指十进制下的进制位。而位的编号从右往左依次 、、、……,后 位表示编号为 ~ 的进制位,第 位表示编号为 的进制位;
题意简化
问: 的正整数次幂(在十进制下)的最后 位的循环长度。如果循环不存在,输出 。
形式化的讲:给定 ,求最小的 ,使对于任意的 ,都有 。若不存在,则输出 。如果想看题目的话,就点击右边:P1050 [NOIP2005 普及组] 循环。
形式化的讲:给定 ,求最小的 ,使对于任意的 ,都有 。若不存在,则输出 。如果想看题目的话,就点击右边:P1050 [NOIP2005 普及组] 循环。
暴力(30pts)
根据同余(取模)的性质,有:
将 ,, 代入 式,得:。所以,已知 ,乘上 (这里也可以提前将 对 取模,可以利用 式在刚开始就一边一位一位地输入一边对 取模)后再对 取模,即可得到 。
所以,让 从 到一个很大的数循环,当第一次出现满足 的 时,答案 就是 ,可以直接输出并结束运行。问题就是:如何判断无解呢?因为总共有 种模 的结果,所以考虑最极端的情况,前 个 (即:)时,都有 。则若存在循环,必有 ,即:在上面最极端的情况成立的情况下需要存在循环,在 时,必有 。即:最多只需要循环 次,仍然没有结束运行的话,就是无解,输出 。
这里给出一个代码片段:
CPP所以,让 从 到一个很大的数循环,当第一次出现满足 的 时,答案 就是 ,可以直接输出并结束运行。问题就是:如何判断无解呢?因为总共有 种模 的结果,所以考虑最极端的情况,前 个 (即:)时,都有 。则若存在循环,必有 ,即:在上面最极端的情况成立的情况下需要存在循环,在 时,必有 。即:最多只需要循环 次,仍然没有结束运行的话,就是无解,输出 。
这里给出一个代码片段:
while(k--) m *= 10;
n %= m;
s = n;
for(int i = 1; i <= m; i ++) {
s = s * n % m;
if(s == n) {
cout << i;
return 0;
}
}
cout << -1;
return 0;
当然,这样只有 30pts。
正解(递推法)
令 表示后 位的 最小 循环长度(设 ,若后 位不存在循环,则令 (方便日后计算、描述))。
问:若现已知 ,是否能够求出 ?
问:若现已知 ,是否能够求出 ?
引理 1:若 ,则必有 。
证明:
因为 ,所以存在后 位长度为 的长度最小循环。因此,后 位也存在一个长度为 的循环(不一定是最小),即:后 位存在循环,即:。
又因为后 存在长度为 的长度最小循环,而后 位的所有循环长度必定是 的正整数倍,且后 位存在一个长度为 的循环。所以 。
证明:
因为 ,所以存在后 位长度为 的长度最小循环。因此,后 位也存在一个长度为 的循环(不一定是最小),即:后 位存在循环,即:。
又因为后 存在长度为 的长度最小循环,而后 位的所有循环长度必定是 的正整数倍,且后 位存在一个长度为 的循环。所以 。
既然有了引理 1,我们就会想:当 , 时, 是 的多少倍呢?
令 ,满足 (由引理 1,当 时,;而当 时,(这一点更重要)。而 是一定唯一的),则有 ,。所以,只要能够求出 ,就能够求出 了。
那么,如何求出 呢?当然 可能为 ,此时代表无解。能否暴力枚举呢?那我们需要一个界限,我们来计算一下 的最大值是多少。
注意到,在考虑后 位时,长为 的循环节中的后 位将长为 的循环节重复 。所以,要确定 的上界,第 位肯定是关键。即:当出现第一个(最小的),使 的第 位与 的第 位相等时,。要求 的上限,即求 时, 的最大值。
同【暴力】,因为一位上一共只有 ~ 十种可能,所以考虑最极端的情况。前 个 ()时,都有: 的第 位与 的第 位不相等。则若 ,当 时,必有: 的第 位与 的第 位相等。即: 时, 的最大值为 。即: 的最大值为 。
所以,枚举 ,从 到 ,判断是否满足 的第 位与 的第 位相等。若相等,则退出循环;若直至循环结束都没有一个在枚举范围内的满足条件的 ,则 。
而 ,所以此时将 代入这个式子,就可以求出所求的 了。
当 时,就输出 ,否则输出 。
当然, 当且仅当存在一个 ,且 ,满足 时才成立。所以,如果在计算时已经遇到了 的情况,可以直接输出 并结束运行,如果计算完后,程序仍没有结束运行,则输出 (可以在循环中进行求和)。
不过,上面的思路还是比较“粗”,就是没有明确如何去细化、实现,只能说是一个大纲吧,有很多地方都还没有细化。所以,接下来就细化上面的思路。关于中间求 的第 位的部分,肯定是不能直接求的。然而,我们注意到,这里的 是从 到 进行枚举的,即: 是递增的。所以,就可以根据前一个 时的情况来推出现在的 时的情况。根据同余(取模)的性质,。所以,可以每次求出 ,并在枚举 时计算即可。然而,这样做并不是最优的,可以进一步优化。注意到,在递推求解的时候,外层的 是从 到 进行递推的,也就是说, 跟 一样,都是递增的。所以,可以用类似于 的方法,保留上次的 函数结果,因为 (可以令 )。所以,可以在开始内层循环之前就预处理出 ,在内层循环(枚举 )里就不断地去乘它就可以了。但是,注意到前文说要对 取模,那么模数就不能通用了。所以,我们对 取模(因为 )即可。
但是,这道题的数据范围很惊人,达到了 ,这就意味着,我们必须要用高精度了。那么,在运行过程中,要用到哪些运算呢?可以发现,只有两种:高精乘高精和高精乘低精(都要取模)。那么,在写高精度的时候,就需要写这两种运算了。
好了,来估计一下时间复杂度。外层 从 到 ,内层先是 次高精乘高精,再是最大 次的高精乘高精以及判断,又是一个特判,最后是一次高精乘低精。而一次高精乘高精的时间复杂度是 ,一次高精乘低精的时间复杂度是 ,,所以整体的时间复杂度大约就是 ,能过了。
令 ,满足 (由引理 1,当 时,;而当 时,(这一点更重要)。而 是一定唯一的),则有 ,。所以,只要能够求出 ,就能够求出 了。
那么,如何求出 呢?当然 可能为 ,此时代表无解。能否暴力枚举呢?那我们需要一个界限,我们来计算一下 的最大值是多少。
注意到,在考虑后 位时,长为 的循环节中的后 位将长为 的循环节重复 。所以,要确定 的上界,第 位肯定是关键。即:当出现第一个(最小的),使 的第 位与 的第 位相等时,。要求 的上限,即求 时, 的最大值。
同【暴力】,因为一位上一共只有 ~ 十种可能,所以考虑最极端的情况。前 个 ()时,都有: 的第 位与 的第 位不相等。则若 ,当 时,必有: 的第 位与 的第 位相等。即: 时, 的最大值为 。即: 的最大值为 。
所以,枚举 ,从 到 ,判断是否满足 的第 位与 的第 位相等。若相等,则退出循环;若直至循环结束都没有一个在枚举范围内的满足条件的 ,则 。
而 ,所以此时将 代入这个式子,就可以求出所求的 了。
当 时,就输出 ,否则输出 。
当然, 当且仅当存在一个 ,且 ,满足 时才成立。所以,如果在计算时已经遇到了 的情况,可以直接输出 并结束运行,如果计算完后,程序仍没有结束运行,则输出 (可以在循环中进行求和)。
不过,上面的思路还是比较“粗”,就是没有明确如何去细化、实现,只能说是一个大纲吧,有很多地方都还没有细化。所以,接下来就细化上面的思路。关于中间求 的第 位的部分,肯定是不能直接求的。然而,我们注意到,这里的 是从 到 进行枚举的,即: 是递增的。所以,就可以根据前一个 时的情况来推出现在的 时的情况。根据同余(取模)的性质,。所以,可以每次求出 ,并在枚举 时计算即可。然而,这样做并不是最优的,可以进一步优化。注意到,在递推求解的时候,外层的 是从 到 进行递推的,也就是说, 跟 一样,都是递增的。所以,可以用类似于 的方法,保留上次的 函数结果,因为 (可以令 )。所以,可以在开始内层循环之前就预处理出 ,在内层循环(枚举 )里就不断地去乘它就可以了。但是,注意到前文说要对 取模,那么模数就不能通用了。所以,我们对 取模(因为 )即可。
但是,这道题的数据范围很惊人,达到了 ,这就意味着,我们必须要用高精度了。那么,在运行过程中,要用到哪些运算呢?可以发现,只有两种:高精乘高精和高精乘低精(都要取模)。那么,在写高精度的时候,就需要写这两种运算了。
好了,来估计一下时间复杂度。外层 从 到 ,内层先是 次高精乘高精,再是最大 次的高精乘高精以及判断,又是一个特判,最后是一次高精乘低精。而一次高精乘高精的时间复杂度是 ,一次高精乘低精的时间复杂度是 ,,所以整体的时间复杂度大约就是 ,能过了。
代码实现
上面就是思路了,下面上代码。
CPP#include <bits/stdc++.h>
using namespace std;
int k, f[110];
struct Number {
int l, a[210];
void Clear() {
l = 0;
memset(a, 0, sizeof(a));
}
void Resize() {
if(l > k) l = k;
}
void In() {
string str;
cin >> str;
Clear();
for(int i = str.size() - 1; i >= 0; i --) {
a[++l] = (str[i] - '0');
}
}
void Out() {
for(int i = l; i >= 1; i --) {
cout << a[i];
}
}
void InInt(int x) {
Clear();
while(x) {
a[++l] = x % 10;
x /= 10;
}
Resize();
}
}n, u, v, w, ans;
Number operator * (Number p, Number q) {
Number rhs;
rhs.Clear();
for(int i = 1; i <= p.l; i ++) {
for(int j = 1; j <= q.l; j ++) {
rhs.a[i + j - 1] += (p.a[i] * q.a[j]);
rhs.a[i + j] += (rhs.a[i + j - 1] / 10);
rhs.a[i + j - 1] %= 10;
}
}
if(rhs.a[p.l + q.l]) rhs.l = p.l + q.l;
else rhs.l = p.l + q.l - 1;
rhs.Resize();
return rhs;
}
int main() {
n.In();
cin >> k;
n.Resize();
f[0] = 1;
v = n;
ans.InInt(1);
for(int i = 1; i <= k; i ++) {
u.InInt(1);
for(int j = 1; j <= f[i - 1]; j ++) {
u = u * v;
}
v = u;
w = n;
for(int j = 1; j <= 10; j ++) {
w = w * u;
if(w.a[i] == n.a[i]) {
f[i] = j;
break;
}
}
if(!f[i]) {
cout << -1;
return 0;
}
w.InInt(f[i]);
ans = ans * w;
}
ans.Out();
return 0;
}
代码仅供参考,不喜勿喷。
数据通过情况
最后的话
这道题主要考到了:递推和高精度。关于代码,还是自己写,不要抄!最后的最后,写篇文章不容易,可否来个赞?
相关推荐
评论
共 5 条评论,欢迎与作者交流。
正在加载评论...