【BZOJ 4563 放棋子】【错排】

BZOJ 4563 放棋子

每行每列有一个障碍,所以我们发现任意交换两行或者两列对答案没有影响,不妨把障碍交换到主对角线位置,就变成了错排问题。

用 F[n] 来表示 n 个元素的错排方案,F[n] = (n - 1) * (F[n - 1] + F[n - 2])

理解一下:
如果我们把第 n 个元素放在第 k 个位置( k 有 n - 1 种取值)

  1. 把第 k 个元素也放在第 n 个位置,则有 F[n - 2] 种方案【把剩下的元素错排】

  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(划掉
高精度计算

麦森数

  1. 平时我们估计一个十进制数 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。

    解释一下求位数的公式

    1. log a(b) 的结果 k 表示 a 的 k 次方等于 b
    2. 存在一个换底公式:log a(c) / log b(c) = log b(c)
    3. 显然 log 10(k) 向上取整表示 k 的位数
    4. log 的运算满足规律 log a(b * c) = log a(b) + log a(c)
    5. 由 4 得出,log a(b ^ c) = log a(b) * c

    所以,(log 10(2) * n) + 1 可以表示 2 ^ n 的位数
    (+ 1 的原因很显然啊

  2. 第二问若用高精加法,时间复杂度为 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;  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值