NC19999 [HAOI2016]放棋子
题目
题目描述
给你一个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})\) 。小小解释一下:
- \((n-1)D_{n-2}\) 表示第 \(n\) 个数和前面 \(n-1\) 个数交换,交换的那个数呆在第 \(n\) 个位置,剩下的错排。
- \((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;
}
NC19999 [HAOI2016]放棋子的更多相关文章
- 【BZOJ4563】[Haoi2016]放棋子 错排+高精度
[BZOJ4563][Haoi2016]放棋子 Description 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍 ...
- 洛谷P3182 [HAOI2016]放棋子
P3182 [HAOI2016]放棋子 题目描述 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要 ...
- bzoj4563: [Haoi2016]放棋子(错排+高精)
4563: [Haoi2016]放棋子 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 387 Solved: 247[Submit][Status] ...
- [Haoi2016]放棋子 题解
4563: [Haoi2016]放棋子 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 440 Solved: 285[Submit][Status] ...
- BZOJ4563: [Haoi2016]放棋子
Description 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在 这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足每行 ...
- [HAOI2016] 放棋子及错排问题
题目 Description 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足 ...
- BZOJ4563:[HAOI2016]放棋子——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=4563 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列 ...
- BZOJ 4563: [Haoi2016]放棋子
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 389 Solved: 248[Submit][Status][Discuss] Descriptio ...
- BZOJ——T 4563: [Haoi2016]放棋子
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 387 Solved: 247[Submit][Status][Discuss] Descriptio ...
- 洛谷 P3182 [HAOI2016]放棋子(高精度,错排问题)
传送门 解题思路 不会错排问题的请移步——错排问题 && 洛谷 P1595 信封问题 这一道题其实就是求对于每一行的每一个棋子都放在没有障碍的地方的方案数. 因为障碍是每行.每列只有一 ...
随机推荐
- TICK 中Kapacitor功能和使用说明
转载请注明出处: 1.Kapacitor 简介 Kapacitor是InfluxData公司开发的一个实时流数据处理引擎.它可以实时地通过TICK脚本处理InfluxDB中的流数据以及批处理数据. K ...
- kafka 的基本概念及使用场景
本文为博主原创,未经允许不得转载: 1. Kafka 的使用场景: 1.日志收集:一个公司可以用Kafka收集各种服务的log,通过kafka以统一接口服务的方式开放给各种 consumer,例如ha ...
- Python定位错误:段错误 (核心已转储)
技术背景 在各种编程语言中都有可能会遇到这样一个报错:"段错误 (核心已转储)".显然是编写代码的过程中有哪里出现了问题,但是这个报错除了这几个字以外没有任何的信息,我们甚至不知道 ...
- 【TouxhGFX】集成 之 《Using C code with TouchGFX》
在TouchGFX中使用C代码 您可能已经知道,TouchGFX是用C ++实现的,而TouchGFX API也是C ++.这意味着至少直接与UI相关的代码必须是C ++代码.但是,并不需要整个系统都 ...
- VUE - 配置跨域
'/api': { target: 'http://localhost:8088/', //这里后台的地址模拟的;应该填写你们真实的后台接口 changOrigin: true, //允许跨域 pat ...
- web-云部署上线
- Oracle session的sid与serial的简单学习
Oracle session的sid与serial的简单学习 ITPUB vage的说法 这样说吧,Oracle允许的会话数(或者说连接数)是固定的,比如是3000个.假设每个会话要占1K字节,哪一共 ...
- Jmeter学习之六_进行https证书处理的工作
Jmeter 进行https证书处理的工作 背景 继续学习中,想着能够抓取一下https相关的信息 所以计划些一下处理过程 但是感觉自己这一块比较薄弱. 场景设计这一块应该是专业人去搞, 我这边先只是 ...
- [转帖]linux中Shell日期转为时间戳的方法
http://www.nndssk.com/xtwt/169617hFPRvq.html shell中获取时间戳的方式为:date -d "$currentTime" +%s $ ...
- [转帖]Linux性能测试之LTP
https://www.modb.pro/db/487946 hello,大家好,今天为大家更新一篇关于Linux性能测试的文章,大家都知道在Windows下测试计算机的性能,我们可以使用鲁大师等软件 ...