P8714 题解
Posted lw0的博客园据点
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P8714 题解相关的知识,希望对你有一定的参考价值。
洛谷 P8714
题意
自己看(
思路
分五个小题去考虑。
问题 A
枚举门牌号,看门牌号中有多少个 \\(2\\),统计答案即可。
void sloveA () // 问题 A
int sum = 0;
for (int i = 1, j; i <= 2020; i++) // 枚举门牌号
j = i;
while (j) // 枚举每一位
sum += (j % 10 == 2); // 统计 2 的数量
j /= 10;
cout << sum;
输出答案:\\(624\\)。
问题 B
枚举分子和分母,判断分子分母是否互质,统计互质的分子分母对数。
int gcd (int x, int y) // 欧几里得算法求最小公因数
return (y ? gcd(y, x % y) : x);
void sloveB () // 问题 B
int sum = 0;
for (int i = 1; i <= 2020; i++)
for (int j = 1; j <= 2020; j++) // 先枚举分子分母
sum += (gcd(i, j) == 1); // 判断是否互质
cout << sum;
输出答案:\\(2481215\\)。
问题 C
开始有难度了哟。
首先画一个草图:
经过观察,我们可以发现:对于每个整数 \\(x\\),位于 \\((x,x)\\) 的数所在的红线箭头都是从下往上的。
那么答案要求的那个数必然也在一条从下往上的红线上。
再观察一下,贯穿 \\((x,x)\\) 的红线起始点在 \\((2x - 1,1)\\) 上。
再推一下,假设我们现在求的是位于 \\((3,3)\\) 的数,可以发现:
这里有一个三角形,三角形中的数的个数再加上 \\(x\\) 就是答案!三角形中的数的个数也很好求,就是 \\(1 + 2 + 3 + \\cdots + (2x - 2)\\)。
综上所述,求位于 \\((x,x)\\) 的数的公式就是 \\((1 + 2x - 2) \\times (2x - 2) / 2 + x\\)。
void sloveC () // 问题 C
int x = 20 * 2 - 1;
cout << (x * (x - 1) / 2 + 20); // 套用公式
输出答案:\\(761\\)。
问题 D
模拟一下,有些细节,处理号闰年和星期等问题就行了。具体看代码。
const int D[13] = 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31; // 每一个月的天数
bool leap (int y) // 判断闰年,如果是闰年则返回值为 1,否则返回值为 0
if (y % 100) // 不是整百年
return !(y % 100 % 4);
y /= 100; // 是整百年
return !(y % 4);
bool check (int y, int m, int d) // 判断是否已经求完了所有日子。
return !(y == 2020 && m == 10 && d == 2); // 因为包含了 2020.10.1,所以要往后一天
void sloveD () // 问题 D
int y = 2000, m = 1, d = 1, z = 6, ans = 0; // y 为年份,m 为月份,d 为日期,z 为星期几,ans 为答案
while (check(y, m, d)) // 判断
ans++; // 每天至少跑 1 km
if (z == 1 || d == 1) // 特殊日子
ans++; // 再跑 1 km
d++, z = z % 7 + 1; // 处理日期变更
if (m != 2 || !leap(y)) // 不是二月或不是闰年
if (d == D[m] + 1) // 正常处理
m++, d = 1;
else // 是闰二月
if (d == 30) // 特殊处理
m++, d = 1;
if (m == 13) // 一年又过完了
y++, m = 1; // 新的一年!
cout << ans;
输出答案:\\(8879\\)。
问题 E
看了一下题解区,实现也没简单到哪去,枚举一下每个二极管亮或不亮,从某一个亮着的二极管开始做一次深搜,判断是否可以只通过亮着的二极管走到所有二极管。
先建一张图我蠢了用了个邻接表,可以通过枚举二进制数来方便的枚举每种情况,统计答案即可。
void dfs (int x) // 简单 dfs
if (vis[x] || !f[x])
return ;
vis[x] = 1, num++;
for (int i : v[x])
dfs(i);
void sloveE () // 问题 E
int sum = 0;
pret(); // 预处理
for (int i = 1; i < (1 << 7); i++) // 枚举二进制对应的十进制数
int st = 7, cnt = 0; // st 为任意的一个亮着的二极管,我就编号取最小的一个
num = 0;
for (int j = 0; j < 7; j++) // 二进制拆分
f[j] = ((i >> j) & 1), vis[j] = 0;
if (f[j])
st = min(st, j), cnt++;
dfs(st); // 做一次深搜
sum += (num == cnt); // 如果能够找到所有发光二极管,答案加 1
cout << sum;
输出答案:\\(80\\)。
好了,到这里,所有小题全部解决了,总体代码 \\(126\\) 行,对我来说是比较长的了。
完整代码
#include <iostream>
#include <vector>
using namespace std;
const int D[13] = 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31; // 每一个月的天数
char c;
vector<int> v[10];
bool vis[10], f[10];
int num;
void pret () // 日常犯蠢 1/1
v[0].push_back(1), v[0].push_back(5);
v[1].push_back(0), v[1].push_back(2), v[1].push_back(6);
v[2].push_back(1), v[2].push_back(3), v[2].push_back(6);
v[3].push_back(2), v[3].push_back(4);
v[4].push_back(3), v[4].push_back(5), v[4].push_back(6);
v[5].push_back(0), v[5].push_back(4), v[5].push_back(6);
v[6].push_back(1), v[6].push_back(2), v[6].push_back(4), v[6].push_back(5);
int gcd (int x, int y) // 欧几里得算法求最小公因数
return (y ? gcd(y, x % y) : x);
bool leap (int y) // 判断闰年,如果是闰年则返回值为 1,否则返回值为 0
if (y % 100) // 不是整百年
return !(y % 100 % 4);
y /= 100; // 是整百年
return !(y % 4);
bool check (int y, int m, int d) // 判断是否已经求完了所有日子。
return !(y == 2020 && m == 10 && d == 2); // 因为包含了 2020.10.1,所以要往后一天
void dfs (int x) // 简单 dfs
if (vis[x] || !f[x])
return ;
vis[x] = 1, num++;
for (int i : v[x])
dfs(i);
void sloveA () // 问题 A
int sum = 0;
for (int i = 1, j; i <= 2020; i++) // 枚举门牌号
j = i;
while (j) // 枚举每一位
sum += (j % 10 == 2); // 统计 2 的数量
j /= 10;
cout << sum;
void sloveB () // 问题 B
int sum = 0;
for (int i = 1; i <= 2020; i++)
for (int j = 1; j <= 2020; j++) // 先枚举分子分母
sum += (gcd(i, j) == 1); // 判断是否互质
cout << sum;
void sloveC () // 问题 C
int x = 20 * 2 - 1;
cout << (x * (x - 1) / 2 + 20); // 套用公式
void sloveD () // 问题 D
int y = 2000, m = 1, d = 1, z = 6, ans = 0; // y 为年份,m 为月份,d 为日期,z 为星期几,ans 为答案
while (check(y, m, d)) // 判断
ans++; // 每天至少跑 1 km
if (z == 1 || d == 1) // 特殊日子
ans++; // 再跑 1 km
d++, z = z % 7 + 1; // 处理日期变更
if (m != 2 || !leap(y)) // 不是二月或不是闰年
if (d == D[m] + 1) // 正常处理
m++, d = 1;
else // 是闰二月
if (d == 30) // 特殊处理
m++, d = 1;
if (m == 13) // 一年又过完了
y++, m = 1; // 新的一年!
cout << ans;
void sloveE () // 问题 E
int sum = 0;
pret(); // 预处理
for (int i = 1; i < (1 << 7); i++) // 枚举二进制对应的十进制数
int st = 7, cnt = 0; // st 为任意的一个亮着的二极管,我就编号取最小的一个
num = 0;
for (int j = 0; j < 7; j++) // 二进制拆分
f[j] = ((i >> j) & 1), vis[j] = 0;
if (f[j])
st = min(st, j), cnt++;
dfs(st); // 做一次深搜
sum += (num == cnt); // 如果能够找到所有发光二极管,答案加 1
cout << sum;
int main ()
// freopen("output", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> c;
if (c == \'A\')
sloveA();
else if (c == \'B\')
sloveB();
else if (c == \'C\')
sloveC();
else if (c == \'D\')
sloveD();
else
sloveE();
return 0;
或者:
#include<iostream>
using namespace std;
int ans[5] = 624, 2481215, 761, 8879, 80;
char c;
int main()
cin >> c;
cout << ans[c - \'A\'];
return 0;
题解[P1045] 麦森数
题目
题目描述
形如2^P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2^P-1
不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。
任务:从文件中输入P(1000<P<3100000),计算2^P-1的位数和最后500位数字(用十进制高精度数表示)
输入格式
文件中只包含一个整数P(1000<P<3100000)
输出格式
第一行:十进制高精度数2
P
?1的位数。
第2-11行:十进制高精度数2^p-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)
不必验证2^P-1与P是否为素数。
输入输出样例
输入 #1
1279
输出 #1
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
分析
首先,肯定要用高精。
分析时间:
直接模拟的话,3100000 是 310^6,也就是要成310^6次2。
而麦森数位数为909526,是10^6级别。
所以用高精度每乘一次2,要运算10^6次(高精乘低精,时间复杂度为线性),
乘310^6次2,就要运算310^12次,肯定要超时。
考虑用快速幂。
快速幂,是将310^6次乘2优化到log级别,也就是log2(310^6)大约是22。但是这样就要增加一个高精数组b[]来存2的幂,更新b[]数组也需要大量的时间。
这是更新b[]的代码
void get_cheng_b(){//函数的作用是将b[]平方
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = len2+len2;
else len2 = len2+len2-1;
F(i,1,len2){
b[i] = h[i];
}
}
求2^P的函数:
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = len+len2;
}
else
len = len+len2-1;
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = len2+len2;
else len2 = len2+len2-1;
F(i,1,len2){
b[i] = h[i];
}
}
void get_jian(){
int i=1;
while(ch[i]==0){
ch[i] = 9;
i++;
}
ch[i]--;
if(ch[len] == 0 && i==len) len--;
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
get_cheng_b();//b[]平方
p/=2;
}
get_jian();
}
2的幂,最大更新到2^P(比如P=64时,2^64=2^64),
也就是说,b[]的长度最大是2^(3*10^6),不也还是10^6吗?
每调用一次get_cheng_b()函数,就要运行(10^6)^2次(高精乘高精,时间复杂度是平方级别),
而while循环22次每次调用一个get_cheng_b()和一个get_cheng_ch(),一共要运算22*[(10^6)^2+10^6]次,时间是无法接受的。
咋办?
但是仔细读题,发现题目只让求后500位。也就是说b[]和ch[]只用寸500位的数据
所以时间问题迎刃而解(其实仔细想想问题的含义就不会有时间问题了)。
位数怎么搞?
是2^P-1的位数,与2^P的位数一样。
注意到10^m的位数是m+1。
我们将2^P转化为以10为底,这样次数加一就是位数。
设次数为x,10^x=2^P,
x=log10(2^P)=[log10(2)]*P
输出x+1即可。
还有两个问题:
1.减一的退位问题
发现2的幂末位不可能为0,所以不考虑退位,jian()函数可以去掉。
2.前导零问题
像我这样直接开一个500位的数组,全部初始为0,如果总位数小于500,那前面的0是没动的,直接输出数组元素,不用考虑前导零。
代码改后是这样:
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = min(510,len+len2);
}
else
len = min(510,len+len2-1);
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = min(510,len2+len2);
else len2 = min(510,len2+len2-1);
F(i,1,len2){
b[i] = h[i];
}
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
if(p/2==0) break;
get_cheng_b();//b[]平方
p/=2;
}
}
进一步优化
我从洛谷题解看到,有人两部优化了高精算法:
用一个数组元素存十位数字,只用长度为50的数组就存下了500位;然后把乘2变成乘2^20(这样longlong是完全接受的)。
比模拟乘2快不少,在500位、P=3*10^6数据下时间复杂度与快速幂相当(再加上快速幂岂不是天下无敌?)。
但这样会带来进位的变动和前导零的麻烦。
我就不写了,copy下地址
My AC code:
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define F(i,a,b) for(int i=a;i<=b;i++)
#define UF(i,a,b) for(int i=a;i>=b;i--)
#define N 1000010
#include <time.h>
using namespace std;
int ch[N], p, len=1,b[N], a=2, len2=1;
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = min(510,len+len2);
}
else
len = min(510,len+len2-1);
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = min(510,len2+len2);
else len2 = min(510,len2+len2-1);
F(i,1,len2){
b[i] = h[i];
}
}
void get_jian(){//2^p一定不以0结尾,所以不用这个函数
int i=1;
while(ch[i]==0){
ch[i] = 9;
i++;
}
ch[i]--;
if(ch[len] == 0 && i==len) len--;
}
void print(int a[], int len){
cout<<"len2="<<len<<endl;
UF(i,len,1)
cout<<a[i];
cout << endl;
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
if(p/2==0) break;
get_cheng_b();//b[]平方
p/=2;
// print(b, len2);
}
// get_jian();
}
int main()
{
cin >> p;
double anslen;
anslen=log10(2)*p+1;
cout<<(int)anslen<<endl;
get_mi();
// cout << len << endl;
UF(i,500,2){
cout << ch[i];
if((i-1)%50==0) cout << endl;
}
cout<<ch[1]-1<<endl;
// printf("%lf",(double)clock()/CLOCKS_PER_SEC);
return 0;
}
以上是关于P8714 题解的主要内容,如果未能解决你的问题,请参考以下文章