写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。
不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。
同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。
加油!
Description
成绩 | 0 | 开启时间 | 2021年08月27日 星期五 12:00 |
折扣 | 0.8 | 折扣时间 | 2021年10月1日 星期五 23:59 |
允许迟交 | 否 | 关闭时间 | 2021年10月10日 星期日 23:59 |
小军的军训正在如火如荼的进行着,而他仍然继续着他写日记的伟大事业。
报数永远是军训中很无聊的环节,教官为了让报数不那么无聊,以至于有人真的去“抱树”,他规定了一个船新的报数方案,方案如下:
第一个人报数x的后三位,之后第i个人报第i-1人所报数的x倍的后三位,即第i个人需要报x^i(x的i次方)的后三位。
站在队列中第n位的小军很想知道自己需要报多少,这样他就可以放心走神了,请问小军需要报多少呢?
数据范围与提示:0≤x≤999,1≤n≤10^1000
输出格式:行末无空格,文末有回车。 注意,1的后三位是1,而1001的后三位是001。
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
测试用例 2 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
测试用例 3 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
题意分析:
题目非常简单,讲白了就是让你算,看起来非常人畜无害的一道题却因为这个n的取值范围到了1e1000而变得腥风血雨,这一题也顺理成章地成为了各路神仙八仙过海的舞台。
那么暴力算肯定不行了(废话)。得想想有什么快速的解决方法。在讨论具体的解决方法之前,先讲一讲普遍的优化策略吧。首先是取模运算的一个公式,这个公式感兴趣的同学可以自行证明一下。利用这个公式我们可以证明一个范围更广的定理:对于式子
,我们可以任意地在中间某个项或者某几个项的乘积后面
,式子的值不会改变。对于题目中的
也是同理,我们可以在每一次的计算中只保留后三位,并不影响最后出来的后三位结果(除了前导零的情况可能会不同,但是这种特例我们可以留待后面解决,这里先把一般的规律归纳出来)。然而仅仅这么优化是远远不够的,我们只解决了溢出的问题,然而这个1e1000的恐怖数字并没有得到优化,时间上肯定不能接受,那么究竟要怎么处理呢?
思路一,对于没有数论基础的同学(说的就是我自己),最容易想到的思路应该是“找循环节”。容易想到,的后三位一共只有1000种可能(000到999,同样先不考虑前导0的问题),而一旦出现了重复则必然是一个循环(可以想想为什么,其实很好证明——如果
的后三位与
相等,那么由前面的取模运算公式,
的后三位必然与
的后三位相等,显而易见地会成为一个循环),而且由于总共只有1000种可能,则循环节必然小于等于1000。因此,我们就可以用一个循环链表的形式来存储x的n次幂,即若干个节点作为前导节点,设其长度为
,若干个节点作为循环节点,设其长度为
。这样我们就可以通过
就可以知道究竟答案对应的节点是哪一个。这里涉及到一个高精度减低精度和高精度对低精度取模的运算,都不是很难,大家可以自行研究一下(笔者亲测此做法是可以全绿的)。至于前导0,可以选择先暴力看一下结果是不是超出了三位,如果是的话就不管前导0了,如果不是那更好,直接输出就行了;也可以在存储循环节的时候做点手脚,容易想到,一旦出现了某个数达到三位数,后面出现的所有节点都必然是含有前导0的,所以可以额外维护一个
变量来保存是否有前导0,默认置false,一旦中途算出一个节点超过了1000,此后置true就行。所以最关键的问题就是循环节要如何求,每次都遍历一遍链表看看是否已经存在肯定不合理,我这里采用的方法是用map来做一个伪链表,即map[a] = b代表a的下一个节点是b,这样既可以在很短的时间内查到a是否在map中,也能通过某个节点直接链接到下一个节点。这样我们可以在1000次运算内找到全部的前导节点和循环节点,这个链表就被我们打出来了。
同时这里有一个思路一的变形,暂称为思路一点五,如果你打完表看看这个循环节长度,你会发现它只会是1,2,5,50,100其中之一,换句话说,都是100的约数,所以直接让n模100,之后暴力就可以算出答案了(当然,过程中依然只要保留三位)。但是为什么只会出现这些长度的循环节,我始终给不出严谨的数学证明,如果有兴趣的同学可以试证一下然后告诉我T^T。
当然,思路一在我看来并不好,原因就是代码量太大了(笑),思路一点五我又给不出严谨的证明。我身边很多大佬写出了既简短又快的方法,我在这简单介绍几个:
思路二:运用快速幂定理。(名词解释:快速幂)这个帖子已经讲的很详细了,我就简单讲两点。一个是高精度除以2的操作其实可以直接通过删去n的最后一位来完成,二是对有前导0的数、还有0和1的特殊情况要另外处理。具体的代码我没有写,但是大部分人都告诉我全绿是没问题的,因为会骤降为
,基本上不超过一万,肯定是没问题的。
思路三:运用扩展欧拉定理。(名词解释:扩展欧拉定理及证明)简单来说,就是当时,
,其中
是欧拉函数,值为小于p的各正整数中与p互质的个数。对此题而言,
,同时我们可以预处理出来
,那么这个1e1000的恐怖数字现在连1000都不剩了,整个问题就很轻松地迎刃而解了;而
时,甚至直接暴力就可以解开了。
贴代码:
这里贴个笨比思路一的代码吧,肯定不是最好的,但是能过T^T。
不要妄想读懂我的代码,因为一个月过去我自己也读不太懂了QAQ。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f;
int main(){
//ifstream infile("input.txt", ios::in);
//ofstream outfile("output.txt", ios::out);
int x;
string n;
cin >> x >> n;
void generateMap(int x, map<int, int>& linkMap, int res[2]);
map<int,int> linkMap;
int loopParameter[2];
generateMap(x, linkMap, loopParameter);
bool isBigger(string n, int a);
bool hasForerunner = false;
int getRemainder(string n, int a);
int rem;
if(isBigger(n, loopParameter[0])){
void reduce(string& num1, int num2);
reduce(n, loopParameter[0]);
rem = getRemainder(n, loopParameter[1]);
rem += loopParameter[0];
} else{
rem = getRemainder(n, INF);
}
int search(map<int, int> linkMap, int count, int x, bool& hasForerunner);
int res = search(linkMap, rem, x, hasForerunner);
void output(int res, bool hasForerunner);
output(res, hasForerunner);
return 0;
}
//用于打表,得到一个用map表示的伪链表
void generateMap(int x, map<int, int>& linkMap, int res[2]){
//res[0] stores the length of fore body, res[1] stores the length of loop body
int getNext(int cur, int x);
int cur = x;
while(linkMap.find(cur) == linkMap.end()){
int next = getNext(cur, x);
linkMap.insert(pair<int, int> (cur, next));
cur = next;
}
int pt = x;
int count = 0;
while(pt != cur){
pt = linkMap[pt];
count ++;
}
res[0] = count;
res[1] = linkMap.size() - count;
}
//用于计算某节点的下一个节点
int getNext(int cur, int x){
cur *= x;
return cur % 1000;
}
//高精度减低精度,直接原地改变
void reduce(string& num1, int num2){
string::iterator it;
it = num1.end();
it--;
if(*(it) - '0' > num2)
*(it) -= num2;
else{
*(it) += 10;
*(it) -= num2;
while(true){
it--;
if(*(it) - '0' > 0){
*(it) -= 1;
break;
} else{
*(it) = '9';
}
}
}
}
//返回高精度对低精度求模
int getRemainder(string n, int a){
int pt = 1;
int cur = n[0] - '0';
while(pt != n.size()){
while(cur < a and pt != n.size()){
cur *= 10;
cur += n[pt++] - '0';
}
cur = cur % a;
}
if(cur >= a)
cur = cur % a;
if(cur == 0)
return a;
return cur;
}
//高精度与低精度的比大小
bool isBigger(string n, int a){
int digit = 0;
int ac = a;
while(ac != 0){
ac /= 10;
digit ++;
}
if(n.size() > digit)
return true;
else if(n.size() < digit)
return false;
else{
int rem = 0;
for(auto ch: n){
rem *= 10;
rem += ch-'0';
}
return rem > a;
}
return -1;
}
//在map中查找某个位置的值
int search(map<int, int> linkMap, int count, int x, bool& hasForerunner){
count --;
int pt = x;
if(pt >= 100)
hasForerunner = true;
while(count --){
pt = linkMap[pt];
if(!hasForerunner and pt >= 100)
hasForerunner = true;
}
return pt;
}
//输出
void output(int res, bool hasForerunner){
if(hasForerunner){
int digit = 0;
int rc = res;
while(rc != 0){
rc /= 10;
digit++;
}
if(digit == 0)
cout << "000" << endl;
else if(digit == 1)
cout << "00" << res << endl;
else if(digit == 2)
cout << "0" << res << endl;
else
cout << res << endl;
} else{
cout << res << endl;
}
}