3 acwing.1213. 斐波那契
第五届蓝桥杯省赛C++A组,第五届蓝桥杯省赛JAVAA组
1 | 斐波那契数列大家都非常熟悉。它的定义是: |
思路:
参考题解:https://www.acwing.com/solution/content/43729/
y总说了,这题巨难,做好心理准备。(看着寥寥无几的题解和通过数就知道了)
应该是我目前为止遇到最难的题了。(蓝桥杯不简单)
难点:数据范围特大,需要取2次模,多组测试数据。(斐波那契数列可能没你想的那么简单!)
题目涉及到斐波那契求前n项和,可以参考 acwing.1303. 斐波那契前 n 项和。(矩阵快速幂)
前n项和Sn和第m项f(m)都可能会非常大,先考虑Sn % f(m)有没有公式表示,因为它不可能直接存下来,再考虑它mod p,p相比前面的就小得多。
之前介绍了矩阵快速幂求解前n项和,这里直接推导数学公式计算。(斐波那契前 n 项和公式)
所以:Sn = f(n+2) - 1。
矩阵快速幂求Fibonacci数列第n项就不再介绍了,具体参见蓝桥杯(三七)。
注意到数据范围是10^18,大概是2^60,计算a=a*a时用到两个10^18相乘就是10^36,超过long long的范围,所以需要高精度或者龟速乘来处理。
龟速乘(慢工出细活,时间换精度),原理类似快速幂,将乘法转化成加法来处理爆long long的问题,相应的时间也会从O(1)变成O(logn)。
a(10)*b(10) = a(10)*b(2),eg. a(10)*19(10) = a(10)*10011(2) = 2^0 *a +2^1 *a +2^4 *a
快速幂和龟速乘都用到了倍增思想以及二进制。
注:上面图片中推导过程不太完整,有部分地方没有说明,但是在代码中会完整计算,样例也会基本覆盖所有情况。(再次吐槽,这题真的超级麻烦)
参考:https://www.acwing.com/blog/content/4794/
1 | // 龟速乘与快速幂的对比 |
代码:
注意:long long的输入输出很容易出错,导致整道题错误!!!
mod p与% p的区别:前者是数学上的取模运算,结果>=0且<p;后者是C++中的取模运算,结果可能为负数,需要转化为正数。
代码量和注释量都非常大!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using namespace std;
typedef long long LL;
LL n,m,p;
LL qmul(LL a,LL b){// 龟速乘计算(a*b) % p
LL res = 0;
while (b){
if (b & 1) res = (res + a) % p;// b(2)的末位是1
a = (a + a) % p;
b >>= 1;// b(2)向右移动一位
}
return res;
}
void mul(LL c[][2],LL a[][2],LL b[][2]){// 可以省略第1维,后面不能省略
LL temp[2][2] = {0};
for (int i = 0;i < 2;i++)
for (int j = 0;j < 2;j++)
for (int k = 0;k < 2;k++)
temp[i][j] = (temp[i][j] + qmul(a[i][k],b[k][j])) % p;// 龟速乘替代普通乘
memcpy(c,temp,sizeof temp);// sizeof c是错误的
}
LL F(LL n){// Fn = F1*a^(n-1)
if (!n) return 0;// 特判n=0的情况,否则陷入死循环
LL f[2][2] = {1,1};// 初始矩阵F1 = [1 1;0 0],第1行是f(1),f(2)
LL a[2][2] = {0,1,1,1};// 等价于{{0,1},{1,1}}
for(LL k = n-1; k ;k >>= 1){// 等价于n--;while(n){...k >>= 1}
if (k & 1) mul(f,f,a);
mul(a,a,a);
}
return f[0][0];
}
LL H(LL m,LL k){// 计算(f(m-1)*f(k) - 1) (mod f(m)),利用Fibonacci两数相乘结论
// 参考最后一种情况
if (k % 2)// k是奇数,结果是f(m-k)-1 (mod f(m)),m = k不可能出现,不用特判
{
return F(m-k) - 1;
}
else// k是偶数,结果是f(m)-f(m-k)-1,f(m)=f(m-k)可能成立,需要特判
{
if (k == 0 || (m == 2 && k == 1)) return F(m) - 1;
else return F(m) - F(m-k) - 1;
}
}
LL G(LL n,LL m){// 计算(f(n)-1) mod f(m) = (f(n) mod f(m) - 1) mod f(m)
// 根据n/m,n%m,n/(2m)三个数进行分类讨论,注意是下取整,结果可能出现负数需要特判
if (m % 2 == 0)// m是偶数
{
if (n / m % 2 == 0)// 且n/m是偶数,结果是f(n mod m)-1 (mod f(m))
{ // 特判n整除m的情况,设n = k*m,利用情况1所归纳出的公式可知f(k*m)=f((k-1)*m)*f(m-1)=...=f(m)*xxx
// 所以此时f(n) mod f(m) = 0
if (n % m == 0) return F(m)-1;// F(n % m) = 0,但取模结果为正,所以加上F(m)
else return F(n % m) - 1;
}
else// 且n/m是奇数,结果是f(m-1)*f(n mod m) (mod(f(m))
{
return H(m,n % m);
}
}
else// m是奇数
{
if (n / m % 2 == 0 && n / m / 2 % 2 == 0)// 且n/m和n/(2m)都是偶数,结果是f(n mod m)-1 (mod f(m))
{
if (n % m == 0) return F(m)-1;// F(n % m) = 0,但取模结果为正,所以加上F(m)
else return F(n % m) - 1;
}
else if (n / m % 2 == 0 && n / m / 2 % 2)// 且n/m是偶数,n/(2m)是奇数,结果是f(m)-f(n mod m)-1 (mod f(m))
{
// 特判f(m) = f(n mod m)的情况,只可能是f(1) = f(2)
if (n % m == 1 && m == 2) return F(m)-1;
else return F(m) - F(n % m) - 1;
}
else if (n / m % 2 && n / m / 2 % 2 == 0)// 且n/m是奇数,n/(2m)是偶数,结果是f(m-1)*f(n mod m)-1 (mod(f(m))
{
return H(m,n % m);
}
else// 且n/m是奇数,n/(2m)是奇数,结果是f(m)-[f(m-1]*f(n mod m) (mod(f(m))]-1 (mod(f(m))
{
// 当n mod m 是奇数时,套用结论,f(m-1)*f(n mod m) = f(m- n mod m)
if (n % m % 2)
{
// 特判f(m) = f(m- n mod m)的情况,只可能是f(1) = f(2)
if (m == 2 && m - n % m == 1) return F(m) - 1;
else return F(m) - F(m - n % m) - 1;
}
else// 当n mod m 是偶数时,套用结论,f(m-1)*f(n mod m) = f(m) - f(m- n mod m)
{
// 结果是f(m - n mod m)-1 (mod(f(m)),f(m - n mod m)不可能=0,不用特判
return F(m - n % m) - 1;
}
}
}
}
int main(){
while(scanf("%lld%lld%lld",&n,&m,&p) != EOF){
printf("%lld\n",(G(n+2,m) % p + p) % p);// 最终输出的答案应该是:(f(n+2)-1) mod f(m) mod p
}
return 0;
}
注意: >> 1 和/ 2不总是等价的,比如-1 >> 1 = -1, -1 / 2 = 0。如果不在F(n)中加入n=0的特判,就会陷入死循环!
4 acwing.1206. 剪格子
第四届蓝桥杯省赛C++A/C组,第四届蓝桥杯省赛JAVAA/C组
我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 60。
本题的要求就是请你编程判定:对给定的 m×n 的格子中的整数,是否可以分割为两个连通的部分,使得这两个区域的数字和相等。
如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。
如果无法分割,则输出 0。
1 | 输入格式 |
思路:
坑:n和m是反过来的,n行在后面。
这题难度与斐波那契不相上下。(高难预警)
错误预警:
- 没有判断剪开后只有两个连通块;
- 按照一笔能画完的方式搜索;
- 按照两笔能画完的方式搜索。
以上都是错误的!
蓝桥杯官网仅包含了以左上角为起点,可不重复一笔画完成的裁剪数据,比如题目描述中的图一。
所以说,网上题解水太深,质量参差不齐,你把握不住,还是y总最靠谱!
且看y总的“锯齿状”搜索!
未完待更。
5 acwing.523. 组合数问题
NOIP2016提高组
1 | 输入格式 |
思路:
前置知识:(排列组合忘完了)
参考排列组合相关知识:https://oi-wiki.org/math/combination/
排列的计算公式如下:
组合数计算公式:
如何理解上述公式? 我们考虑 $n$ 个人 $m(m \leq n)$ 个出来,不排队, 不在乎顺序 $C_{n}^{m}$ 。如果在乎排列那么就是 $A_{n}^{m}$, 如果不在乎那么就要除掉重复,那么重复了多少? 同样选出的来的 $m$ 个人, 他们还要”全排”得 $A_{n}^{m}$, 所以得:
参考题解:https://www.acwing.com/solution/content/3157/
根据题意,划定的组合数的i、j的范围限制在蓝色阴影梯形。我们不妨将区域扩展为n*m的整个矩形,在对角线之上的${C}_{i}^{j}$是不合法的,设为0;对于蓝色阴影梯形,如果$C_i^j\ \%\ k = 0$,设为1,否则设为0。所以整个矩形上的每个点都有一个值,0或1。
我们再通过二维前缀和来预处理这个矩形区域,计算1的个数,也就是答案。
接下来的问题是如何计算组合数?
- 通过题目中给出的定义公式,有阶乘的乘法和除法,很容易爆long long,精度可能也不高,比较麻烦。
- 按照递推公式(杨辉三角的公式表达)计算,${C}_{n}^{m}={C}_{n-1}^{m}+{C}_{n-1}^{m-1}$。(时间复杂度:$O(n^2)$)
由于${C}_{n}^{m}$表示从n个不同的数中选择m个数的方案数,因此转化为以下两项之和:一,不选最后一个数,从前n-1个数中选m个数;二,选最后一个数,从前n-1个数中选m-1个数。(参考:算法笔记)
注意:${C}_{n}^{0}={C}_{n}^{n}=1,{C}_{0}^{0}一般约定为1$。
在计算组合数的过程中还需要判断% k的值。
递推出$C_n^m$的时间复杂度是 $O(n^2)$,预处理二维前缀和的时间复杂度也是$O(n^2)$。每次查询时直接查表即可,时间复杂度是 O(1),一共有 t 次询问,因此总时间复杂度是 $O(n^2+t)$。
1 |
|