NC19999 [HAOI2016]放棋子

发布时间 2023-08-26 20:36:14作者: 空白菌

题目链接

题目

题目描述

给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。

输入描述

第一行一个N,接下来一个N*N的矩阵。N ≤ 200,0表示没有障碍,1表示有障碍,输入格式参考样例

输出描述

一个整数,即合法的方案数。

示例1

输入

2
0 1
1 0

输出

1

题解

知识点:排列组合,高精度。

注意到在题目条件下,排列方案数和障碍物出现位置没有任何关系,任何情况都等价于形如下方矩阵的答案:

1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1

我们发现,这个矩阵答案其实就是 \(n\) 个元素的错排数 \(D_n\)

众所周知,\(D_n = (n-1)(D_{n-1} + D_{n-2})\) 。小小解释一下:

  1. \((n-1)D_{n-2}\) 表示第 \(n\) 个数和前面 \(n-1\) 个数交换,交换的那个数呆在第 \(n\) 个位置,剩下的错排。
  2. \((n-1)D_{n-1}\) 表示第 \(n\) 个数和前面 \(n-1\) 个数交换,交换的那个数不在第 \(n\) 个位置,和剩下的一起错排。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

///继承vector解决位数限制(当前最大位数是9倍整型最大值),操作方便(注意size()返回无符号长整型,尽量不要直接把size放入表达式)
struct Huge_Int :vector<long long> {

    static const int WIDTH = 9;///压位数,压9位以下 比较安全
    static const long long BASE = 1e9;///单位基
    static const long long MAX_INT = ~(1 << 31);///最大整型
    bool SIGN;

    ///初始化,同时也可以将低精度转高精度、字符串转高精度
    ///无需单独写高精度数和低精度数的运算函数,十分方便
    Huge_Int(long long n = 0) { *this = n; }
    Huge_Int(const string &str) { *this = str; }

    ///格式化,包括进位和去前导0,用的地方很多,先写一个
    Huge_Int &format(int fixlen = 1) {//去0后长度必须大于等于fixlen,给乘法用的
        while (size() > fixlen && !back()) pop_back();//去除最高位可能存在的0
        if (!back()) SIGN = 0;
        for (int i = 1; i < size(); ++i) {
            (*this)[i] += (*this)[i - 1] / BASE;
            (*this)[i - 1] %= BASE;
        }//位内进位
        while (back() >= BASE) {
            push_back(back() / BASE);
            (*this)[size() - 2] %= BASE;
        }//位外进位
        return *this;//为使用方便,将进位后的自身返回引用
    }

    ///归零
    void reset() {
        clear();
        SIGN = 0;
    }

    ///重载等于,初始化、赋值、输入都用得到
    Huge_Int operator=(long long n) {
        reset();
        SIGN = n < 0;
        if (SIGN) n = -n;
        push_back(n);
        format();
        return *this;
    }
    Huge_Int operator=(const string &str) {
        reset();
        if (str.empty()) push_back(0);
        SIGN = str[0] == '-';
        for (int i = str.length() - 1;i >= 0 + SIGN;i -= WIDTH) {
            long long tmp = 0;
            for (int j = max(i - WIDTH + 1, 0 + SIGN);j <= i;j++)
                tmp = (tmp << 3) + (tmp << 1) + (str[j] ^ 48);
            push_back(tmp);
        }
        format();
        return *this;
    }

    ///重载输入输出
    friend istream &operator>>(istream &is, Huge_Int &tmp) {
        string str;
        if (!(is >> str)) return is;
        tmp = str;
        return is;
    }
    friend ostream &operator<<(ostream &os, const Huge_Int &tmp) {
        if (tmp.empty()) os << 0;
        else {
            if (tmp.SIGN) os << '-';
            os << tmp[tmp.size() - 1];
        }
        for (int i = tmp.size() - 2;i >= 0;i--) {
            os << setfill('0') << setw(WIDTH) << tmp[i];
        }
        return os;
    }

    ///重载逻辑运算符,只需要小于,其他的直接代入即可
    ///常量引用当参数,避免拷贝更高效
    friend bool operator<(const Huge_Int &a, const Huge_Int &b) {
        if (a.SIGN ^ b.SIGN) return a.SIGN;
        if (a.size() != b.size()) return a.SIGN ? a.size() > b.size() :a.size() < b.size();
        for (int i = a.size() - 1; i >= 0; i--)
            if (a[i] != b[i])return a.SIGN ? a[i] > b[i] : a[i] < b[i];
        return 0;
    }
    friend bool operator>(const Huge_Int &a, const Huge_Int &b) { return b < a; }
    friend bool operator>=(const Huge_Int &a, const Huge_Int &b) { return !(a < b); }
    friend bool operator<=(const Huge_Int &a, const Huge_Int &b) { return !(a > b); }
    friend bool operator!=(const Huge_Int &a, const Huge_Int &b) { return a < b || b < a; }
    friend bool operator==(const Huge_Int &a, const Huge_Int &b) { return !(a != b); }

    ///重载负号
    friend Huge_Int operator-(Huge_Int a) { return a.SIGN = !a.SIGN, a; }

    ///绝对值函数
    friend Huge_Int abs(Huge_Int a) { return a.SIGN ? (-a) : a; }

    ///加法,先实现+=,这样更简洁高效
    friend Huge_Int &operator+=(Huge_Int &a, const Huge_Int &b) {
        if (a.SIGN ^ b.SIGN) return a -= (-b);
        if (a.size() < b.size()) a.resize(b.size());
        for (int i = 0; i < b.size(); i++) a[i] += b[i];//被加数要最大位,并且相加时不要用未定义区间相加
        return a.format();
    }
    friend Huge_Int operator+(Huge_Int a, const Huge_Int &b) { return a += b; }
    friend Huge_Int &operator++(Huge_Int &a) { return a += 1; }
    friend Huge_Int operator++(Huge_Int &a, int) {
        Huge_Int old = a;
        ++a;
        return old;
    }

    ///减法,由于后面有交换,故参数不用引用
    friend Huge_Int &operator-=(Huge_Int &a, Huge_Int b) {
        if (a.SIGN ^ b.SIGN) return a += (-b);
        if (abs(a) < abs(b)) {
            Huge_Int t = a;
            a = b;
            b = t;
            a.SIGN = !a.SIGN;
        }
        for (int i = 0; i < b.size(); a[i] -= b[i], i++) {
            if (a[i] < b[i]) {//需要借位
                int j = i + 1;
                while (!a[j]) j++;
                while (j > i) a[j--]--, a[j] += BASE;
            }
        }
        return a.format();
    }
    friend Huge_Int operator-(Huge_Int a, const Huge_Int &b) { return a -= b; }
    friend Huge_Int &operator--(Huge_Int &a) { return a -= 1; }
    friend Huge_Int operator--(Huge_Int &a, int) {
        Huge_Int old = a;
        --a;
        return old;
    }


    ///乘法,不能先实现*=,因为是类多项式相乘,每位都需要保留,不能覆盖
    friend Huge_Int operator*(const Huge_Int &a, const Huge_Int &b) {
        Huge_Int n;
        n.SIGN = a.SIGN ^ b.SIGN;
        n.assign(a.size() + b.size() - 1, 0);//表示乘积后最少的位数(可能会被format消掉,因此添加了format参数)
        for (int i = 0; i < a.size(); i++) {
            for (int j = 0; j < b.size(); j++)
                n[i + j] += a[i] * b[j];
            n.format(n.size());//提前进位
        }
        return n;//最后进位可能会溢出
    }
    friend Huge_Int &operator*=(Huge_Int &a, const Huge_Int &b) { return a = a * b; }

    ///带余除法函数,方便除法和模运算,暂时写不出高效的高精与高精的除法
    friend Huge_Int divmod(Huge_Int &a, const Huge_Int &b) {//O(logn),待修改
        assert(b != 0);
        Huge_Int n;
        if (-MAX_INT - 1 <= b && b <= MAX_INT) {//除数小于等于整型才能用这个,不然会溢出
            n = a;
            n.SIGN = a.SIGN ^ b.SIGN;
            long long rest = 0;
            long long bl = 0;
            for (int i = b.size() - 1;i >= 0;i--) bl = bl * BASE + b[i];
            for (int i = n.size() - 1;i >= 0;i--) {
                rest *= BASE;
                n[i] += rest;
                rest = n[i] % bl;
                n[i] /= bl;
            }
            a = a.SIGN ? (-rest) : rest;
            return n.format();
        }
        else {//考虑倍增或者二分优化
            n.SIGN = a.SIGN ^ b.SIGN;
            for (int i = a.size() - b.size(); abs(a) >= abs(b); i--) {//减法代替除法
                Huge_Int c, d;
                d.assign(i + 1, 0);
                d.back() = 1;
                d.SIGN = n.SIGN;
                c = b * d;//提高除数位数进行减法
                while (abs(a) >= abs(c)) a -= c, n += d;
                d.pop_back();
                if (!d.empty()) {//遍历压的位
                    d.back() = BASE / 10;
                    for (int i = 1;i < WIDTH;i++) {
                        c = b * d;
                        while (abs(a) >= abs(c)) a -= c, n += d;
                        d.back() /= 10;
                    }
                }
            }
            return n;
        }
    }
    friend Huge_Int operator/(Huge_Int a, const Huge_Int &b) { return divmod(a, b); }
    friend Huge_Int &operator/=(Huge_Int &a, const Huge_Int &b) { return a = a / b; }
    friend Huge_Int &operator%=(Huge_Int &a, const Huge_Int &b) { return divmod(a, b), a; }
    friend Huge_Int operator%(Huge_Int a, const Huge_Int &b) { return a %= b; }
};

Huge_Int f[207];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++)
        for (int j = 1, x;j <= n;j++)
            cin >> x;
    f[0] = 1;
    f[1] = 0;
    for (int i = 2;i <= n;i++) f[i] = (i - 1) * (f[i - 1] + f[i - 2]);
    cout << f[n] << '\n';
    return 0;
}