每行每列有一个障碍,所以我们发现任意交换两行或者两列对答案没有影响,不妨把障碍交换到主对角线位置,就变成了错排问题。
用 F[n] 来表示 n 个元素的错排方案,F[n] = (n - 1) * (F[n - 1] + F[n - 2])
理解一下:
如果我们把第 n 个元素放在第 k 个位置( k 有 n - 1 种取值)
把第 k 个元素也放在第 n 个位置,则有 F[n - 2] 种方案【把剩下的元素错排】
把第 k 个元素放在其他位置,则有 F[n - 1] 种方案【也可以看做把 1 ~ n - 1 的元素错排,然后交换 n 和 k】
另外错排公式是:
F[n] = (n !)[(-1) ^ 0 / 0 ! +(-1) ^ 1 / (1 !) + (-1) ^ 2 / (2 !) + (-1) ^ 3 / (3 !) + …… + (- 1) ^ n / (n !)]
特别地有 0 ! = 1,1 != 1
需要高精
(赶紧顺便复习一下高精 qwq(划掉
高精度计算
麦森数
平时我们估计一个十进制数 x (x > 0)的位数,往往潜在地使用了放缩法:
若 10 ^ k <= x < 10 ^ (k + 1),则 x 有 k + 1 位。而当 x = 2 ^ p - 1 时,由于 x 的末位不为 0,因此不存在借位现象,所以 x 的位数等于 2 ^ p 的位数。
又假设 10 ^ k <= 2 ^ p < 10 ^ (k + 1),此中的 k,便等于 [log 10(2 ^ p)] 也就是 int(log 10(2 ^ p))。
最后变换一下,第一问的答案便是 int(p * log 10(2)) + 1。
解释一下求位数的公式
- log a(b) 的结果 k 表示 a 的 k 次方等于 b
- 存在一个换底公式:log a(c) / log b(c) = log b(c)
- 显然 log 10(k) 向上取整表示 k 的位数
- log 的运算满足规律 log a(b * c) = log a(b) + log a(c)
- 由 4 得出,log a(b ^ c) = log a(b) * c
所以,(log 10(2) * n) + 1 可以表示 2 ^ n 的位数
(+ 1 的原因很显然啊第二问若用高精加法,时间复杂度为 O(n)。但常数过大,超时在所难免。
于是想到,要用快速幂高精。
只有一点不同,此处的快速幂是递归形式的。
当由递归得到 2 ^ (p / 2) 的值时,便可以通过 2 ^ p = 2 ^ (p / 2) * 2 ^ (p / 2) 得到 2 ^ p 的值,若 p 是奇数,2 ^ p = 2 ^ (p / 2) * 2 ^ (p / 2) * 2 即可。
以下是递归的框架:
void work(long long n) { // n 即为 p 值
if(n == 0) return ;
work(n / 2);
if(n % 2 == 0)
根据 2 ^ p = 2 ^ (p / 2) * 2 ^ (p / 2),由高精得到 a = b * b;
if(n % 2 == 1)
根据 2 ^ p = 2 ^ (p / 2) * 2 ^ (p / 2) * 2,由高精得到 a = b * b * 2;
处理 a 数组进位,更新 b 数组;
清空 a 数组;
}
代码:
#include <bits/stdc++.h>
using namespace std;
int a[10005], b[10005];
void work(int n) {
if(n == 0) return ;
work(n / 2);
if(n % 2 == 0) {
for(int i = 0; i < 500; i ++)
for(int j = 0; j < 500; j ++)
a[i + j] += b[i] * b[j];
} else if(n % 2) {
for(int i = 0; i < 500; i ++)
for(int j = 0; j < 500; j ++)
a[i + j] += b[i] * b[j] * 2;
}
for(int i = 0 ; i < 500; i ++) {
b[i] = a[i] % 10;
a[i + 1] += a[i] / 10;
}
memset(a, 0, sizeof(a));
}
int main() {
int p;
scanf("%d", &p);
printf("%d\n", (int)(log10(2) * p) + 1);
b[0] = 1;
work(p);
for(int i = 499; i > 0; i --) {
printf("%d", b[i]);
if((i + 1) % 50 == 1) printf("\n");
}
printf("%d\n", b[0] - 1);
return 0;
}
本题代码:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
struct use {
int a[1005]; // a[0] 代表 f 的长度
}f[205];
int n;
use operator + (use a, use b) {
use c; int t = 0;
c.a[0] = max(a.a[0], b.a[0]);
for(int i = 1; i <= c.a[0]; i ++) {
c.a[i] = a.a[i] + b.a[i] + t;
if(c.a[i] >= 10) {
t = c.a[i] / 10;
c.a[i] %= 10;
}
else t = 0;
}
if(t > 0) c.a[++ c.a[0]] = t;
return c;
}
use operator * (int x, use a) { // 高精度 * 单精度
int t = 0;
for(int i = 1; i <= a.a[0]; i ++) a.a[i] *= x;
for(int i = 1; i <= a.a[0]; i ++) {
a.a[i] += t;
if(a.a[i] >= 10) {
t = a.a[i] / 10;
a.a[i] %= 10;
}
else t = 0;
}
while(t) {
a.a[++ a.a[0]] = t % 10;
t /= 10;
}
return a;
}
int main() {
scanf("%d", &n);
f[1].a[1] = 0; f[2].a[1] = f[1].a[0] = f[2].a[0] = 1;
for(int i = 3; i <= n; i ++)
f[i] = (i - 1) * (f[i - 1] + f[i - 2]);
for(int i = f[n]. a[0]; i >= 1 ; i --)
printf("%d", f[n].a[i]);
return 0;
}