JOISC 2016 Day 1 棋盘游戏
题意
JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子。JOI 君有若干棋子,并想用它们来玩一个游戏。初始状态棋盘上至少有一个棋子,也至少有一个空位。
游戏的目标是:在还没有放棋子的格子上依次放棋子,并填满整个棋盘。在某个格子上放置棋子必须满足以下条件之一:
- 这个格子的上下一格都放有棋子;
- 这个格子的左右一格都放有棋子。
JOI 君想知道有多少种从初始状态开始,并达到游戏目标的方案,这个答案可能会非常大。请你帮 JOI 君算出这个答案,并对 \(10^9+7\) 取模。
题解
注意到可以通过上下填的只有第二行。
先判断是否有解。如果第一行或第三行存在两个连续的空格,那么无解,否则有解。那么一三两行可以在任意时刻填棋子。
对第二行的空格连续段分别计算,最后组合即可。
设 \(H(i, r), V(i, r)\) 分别表示考虑到当前连续段第 \(i\) 列,当前中间格在前 \(i\) 列的空格中第 \(r\) 个填,且是依赖左右/上下两边填的方案数。如果四个方向都有棋子,则填上下两边。
设第 \(i\) 列中第一、三行共 \(t\) 个空位。
分三种情况考虑:
-
\(i, i - 1\) 两列均为纵向,则相互不影响。
\[V(i, r) \gets (r-1)^\underline{t}\sum_x V(i - 1, x) \]其中下降幂为 \(i\) 上下的方案数。
-
\(i\) 列为纵向,\(i-1\) 列为横向,则 \(i\) 列必须早于 \(i - 1\) 列填。
\[V(i, r) \gets (r-1)^\underline{t} \sum_{x \ge i - t} H(i - 1, x) \] -
\(i\) 列为横向,\(i-1\) 列为纵向。
此时需要保证填中间格是上下格不能已经全部有棋子,且左边的中间格已经有棋子。
为了避免讨论相对顺序,我们先只加入中间格,然后再加入对顺序没有影响的上下格。
设上下中有 \(c\) 个在中间之前填:
\[H(i, r + c) \gets \sum_{x<r} V(i - 1, x) \cdot E(r - 1, c) \cdot E(\mathrm{cnt} - r, t - c) \cdot t! \]其中 \(r + c\) 为加入上下格后对 \(i\) 的排名的影响,\(\mathrm{cnt}\) 为当前连通块中总空格数,\(E(i, j)\) 表示在 \(i\) 个元素之间的 \(i + 1\) 个空格中放置 \(j\) 个相同物品的方案数。
使用前缀和,总时间复杂度 \(\mathcal O(n^2)\)。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <numeric>
#include <vector>
#include <cassert>
using namespace std;
#define LOG(f...) fprintf(stderr, f)
#define all(cont) begin(cont), end(cont)
// #define DBG(f...) printf("L %4d: ", __LINE__), printf(f)
#define DBG(f...) void()
char getv() {
char ch;
while (isspace(ch = getchar()));
return ch;
}
using ll = long long;
const int N = 2005;
const int M = 1000000007;
char a[N][3];
int dp[N][N * 3][2];
vector<int> fac, ifac;
int sub(int x, int y) { return x < y ? x - y + M : x - y; }
void inc(int &x, int y) { if ((x += y) >= M) x -= M; }
int power(int x, int y) {
int p = 1;
for (; y; y >>= 1, x = (ll)x * x % M)
if (y & 1) p = (ll)p * x % M;
return p;
}
int inv(int x) { return power(x, M - 2); }
void prefac(int n) {
fac.resize(n + 1); ifac.resize(n + 1);
fac[0] = 1;
for (int i = 1; i <= n; ++i)
fac[i] = (ll)fac[i - 1] * i % M;
ifac[n] = inv(fac[n]);
for (int i = n; i; --i)
ifac[i - 1] = (ll)ifac[i] * i % M;
}
int binom(int n, int m) { return (ll)fac[n] * ifac[m] % M * ifac[n - m] % M; }
int ff(int n, int m) { return (ll)fac[n] * ifac[n - m] % M; }
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int n;
scanf("%d", &n);
prefac(3 * n);
for (int c = 0; c < 3; ++c)
for (int i = 1; i <= n; ++i)
a[i][c] = getv();
for (int i = 1; i <= n; ++i)
if ((a[i][0] == 'x' && (a[i - 1][0] != 'o' || a[i + 1][0] != 'o')) ||
(a[i][2] == 'x' && (a[i - 1][2] != 'o' || a[i + 1][2] != 'o'))) {
puts("0");
return 0;
}
int empt = 0, prod = 1;
int siz = 0;
for (int i = 1; i <= n; ++i) {
int t = int(a[i][0] == 'x') + int(a[i][2] == 'x');
empt += t + int(a[i][1] == 'x');
if (a[i][1] != 'x') // skip
siz = 0;
else if (a[i - 1][1] != 'x') { // init
siz = t + 1;
for (int j = t + 1; j <= siz; ++j)
dp[i][j][0] = ff(j - 1, t);
if (i != 1)
for (int c = 1; c <= t; ++c)
for (int j = t - c + 1; j <= siz - c; ++j)
dp[i][j][1] = (dp[i][j][1] + (ll)binom(j - 1, t - c) * binom(siz - j, c) * fac[t]) % M;
}
else {
int psum0 = dp[i - 1][siz][0], psum1 = dp[i - 1][siz][1];
++siz;
for (int c = 0; c < t; ++c)
for (int j = 1; j <= siz; ++j)
dp[i][j + c][1] = (dp[i][j + c][1] + (ll)dp[i - 1][j - 1][0] * (c == 0 ? 1 : j) % M * binom(siz - j + t - c, t - c) * fac[t]) % M;
siz += t;
for (int j = t + 1; j <= siz; ++j)
dp[i][j][0] = ((ll)psum0 * ff(j - 1, t) + (ll)sub(psum1, dp[i - 1][j - t - 1][1]) * ff(j - 1, t)) % M;
}
if (a[i][1] == 'x')
for (int j = 2; j <= siz + 3; ++j)
inc(dp[i][j][0], dp[i][j - 1][0]), inc(dp[i][j][1], dp[i][j - 1][1]);
if (a[i + 1][1] != 'x' && a[i][1] == 'x') {
int ways = dp[i][siz][0];
if (i != n) inc(ways, dp[i][siz][1]);
prod = (ll)prod * ways % M * ifac[siz] % M;
siz = 0;
}
}
prod = (ll)prod * fac[empt] % M;
printf("%d\n", prod);
return 0;
}
原文地址:https://www.cnblogs.com/RiverHamster/p/sol-loj2731.html
- 还有5天,你的比特币最重要的孩子UB-UBTC 可能就永远不属于你了
- Spark SQL 用户自定义函数UDF、用户自定义聚合函数UDAF 教程(Java踩坑教学版)
- Webpack多入口文件、热更新等体验
- 从hello world 解析程序运行机制
- 万达大量员工“被”辞职?曲德君回应:万达网科没有倒
- iOS Programming – 触摸事件处理(2)
- 洋葱海外仓融资2亿元 官网启用msyc.cc域名
- Webpack单元测试,e2e测试
- [看图说话] 基于Spark UI性能优化与调试——初级篇
- Unix下c程序内存泄露检测工具
- 查找链表中倒数第k个结点
- 12日29日人力科技前沿资讯
- 一段奇葩Javascript代码引发的思考
- 路由器、集线器、网关、网桥的联系与区别
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 不会吧!都 0202 年了,不会还有不知道 axios 的吧
- 全栈的自我修养: 002使用@vue/cli进行vue环境搭建 (使用Vue,SpringBoot,Flask完成前后端分离)
- JAVA | Java 解决跨域问题 花式解决跨域问题
- 深入理解Java泛型(二)通配符与嵌套
- 用高斯计算磷光发射能
- Android So动态加载 优雅实现与原理分析
- 堆与栈区别
- 在Gaussian16中同时扫描两个反应坐标
- python调试神器traceback
- Centos安装高版本GCC
- Django+Vue开发生鲜电商平台之5.使用DRF实现商品列表页和过滤
- Spring 注入集合的成员变量属性
- 深入理解JVM(③)Java的锁优化
- 解Bug之路-Nginx 502 Bad Gateway
- bootstrap v4 toast轻提示正确用法