NOIP2018 Day2T2 填数游戏
下面先给出大家都用的打表大法:
首先我们可以发现 \(n \le 3\) 的情况有 \(65pts\),而 \(n\) 这么小,打一下表何乐而不为呢?于是我写了一个爆枚每个位置再 \(check\) 的 \(dfs\),复杂度 \(O(2 ^ {nm + n + m} \log 2 ^ {n + m - 1})\) 因为 \(n\) 实在太小了于是我打出了下面的表(\(1\) 的情况直接算就没打了,下面第一个数都是从 \(m = n\) 开始的)
2 : 12 36 108 324 ...
3 : 112 336 1008 3024 ...
很明显可以发现这里每个答案往后都乘了 \(3\),于是我们只需要写一个快速幂就可以获得 \(65pts\) 的高分。之后我算出了 \((4, 4)\) 的答案 \(912\),又算了 \((4, 5)\) 的答案想要验证乘 \(3\) 的猜想,但 \((4, 5)\) 却是 \(2688 \ne 3 \times 912\),然后我再想跑出 \((4, 6)\) 的答案,可是目前的 \(dfs\) 大概要跑 \(2h\) 左右,于是我再分析了一下样例,发现实际上题目的要求是优先往右走的路径的 \(01\) 字典序要不大于优先往下走的所有路径 \(01\) 字典序,因此我有了这样一个剪枝方法,我们先暴力枚举第一列的所有数,然后从最后一行开始暴力枚举,吧每一条路径压乘十进制数在树状数组上修改,那么再一遍遍爆枚上一层,记录一下每个位置到终点的最大 \(01\) 字典序加到当前的字典序后面然后再在线段树上查询是否有小于当前字典序的串,如果有剪枝即可。这个东西看起来很难实现,而且复杂度也很玄学,回头想一下题目的条件,要求优先往右走的字典序小,那么假设现在有两个 \(01\) 串 \(a, b\) 前者优先往下走,后者优先往右走,假设目前两串相同,如果下面 \(a < b\) 那么后者必然走到 \(1\) 而前者必然走到 \(0\),再注意到 \(a, b\) 长度应该相等也就意味着两者走的步数相同,那么两者应该都在左下到右上的一条对角线上,因此我们可以得出一个结论,每一条左下到右上的对角线都是先一段 \(1\) 后一段 \(0\)。那么我们就可以有了一种新的爆搜方式,我们爆枚每个对角线 \(0, 1\) 分界点,再暴力 \(check\) 这样复杂度是 \(O((m!) ^ 2 2 ^ {n + m} \log 2 ^ {n + m})\),算了一下这东西能跑出 \((6, 6)\),于是我实现了这个 \(dfs\),发现 \((6, 6)\) 只需要 \(15s\),于是我让他去跑 \((7, 7)\) 打出了 \(n \le 6\) 的表。
2 : 12 36 108 324 972 2916 ...
3 : 112 336 1008 3024 9072 27216 ...
4 : 912 2688 8064 24192 72576 217728 ...
5 : 7136 21312 63936 191808 ...
6 : 56768 170112 510336 ...
可以发现,虽然 \((4, 4) \rightarrow (4, 5)\) 不是乘 \(3\),但 \((4, 5) \rightarrow (4, 6), (4, 6) \rightarrow (4, 7)\) 接下来都是乘 \(3\),对于 \(5, 6\) 也是一样。于是我们大胆地猜测剩下的所有情况应该都是从第二项开始乘 \(3\) 的。那我们只需要求出第一项和第二项即可。
过了 \(20min\) 终于跑出了 \((7, 7)\),现在的表如下:
2 : 12 36 108 324 972 2916 ...
3 : 112 336 1008 3024 9072 27216 ...
4 : 912 2688 8064 24192 72576 217728 ...
5 : 7136 21312 63936 191808 ...
6 : 56768 170112 510336 ...
7 : 453504 ...
感觉 \((7, 8)\) 应该是死都跑不出来了,但是我们打表出了这么多数据,可以找一下规律。我们发现 \(n \ge 4\) 时虽然第一项乘 \(3\) 不等于第二项,但隔得非常近,于是我算出了 \(n = 4\) 时 \(912 \times 3 - 2688 = 48\),接着再算了 \(n = 5\) 时 \(7136 \times 3 - 21312 = 96\),然后又算了 \(n = 6\) 时 \(56768 \times 3 - 170112 = 192\),规律非常明显了,每次插值乘了 \(2\),并且可以发现 \(48 = 3 \times 2 ^ 4, 96 = 3 \times 2 ^ 5\),于是我们可以发现 \((n, n + 1) = 3 \times (n, n) - 3 \times 2 ^ n\),那么我们只需要跑出 \((8, 8)\) 这题就做完了,然而貌似 \((8, 8)\) 根本跑不出...
应该还有规律,继续观察。可以发现 \(m \ge n + 2\) 的情况都是从 \((n, n + 1)\) 推来的,而 \((n, n + 1)\) 与 \((n, n)\) 有关,于是我们可以考虑只观察前面两项,观察一段时间后可以发现 \(((n, n) + (n, n + 1)) \times 2\) 与 \((n + 1, n + 1)\) 非常接近,于是我又算了他们的差值可以发现 \(4 \rightarrow 5 = 64, 5 \rightarrow 6 = 128, 6 \rightarrow 7 = 256\),规律也非常明显,每次乘了 \(2\),并且可以发现每次的插值 \(i - 1 \rightarrow i = 2 ^ {i + 1}\),那么我们岂不是知道前两项的递推式了?令 \(f_{i, j}\) 为 \((i, j)\) 的答案,于是我们有 \(f_{i, i} = (f_{i - 1, i - 1} + f_{i - 1, i}) \times 2 - 2 ^ {i + 1}, f_{i, i + 1} = 3 \times f_{i, i} - 3 \times 2 ^ i\),于是我们直接矩阵快速幂递推出前两项,后面的直接使用快速幂即可。
#include<bits/stdc++.h>
using namespace std;
#define N 5
#define Mod 1000000007
#define rep(i, l, r) for(int i = l; i <= r; ++i)
struct mat{
int g[N][N];
void clear(){ memset(g, 0, sizeof(g));}
}f, tr, ans;
int n, x, m;
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
return (a -= b) < 0 ? a + Mod : a;
}
int Mul(int a, int b){
return 1ll * a * b % Mod;
}
int Qpow(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = Mul(ans, a);
a = Mul(a, a), b >>= 1;
}
return ans;
}
mat mul(mat a, mat b){
mat c; c.clear();
rep(i, 1, 3) rep(j, 1, 3) rep(k, 1, 3) c.g[i][j] = Inc(c.g[i][j], Mul(a.g[i][k], b.g[k][j]));
return c;
}
int main(){
n = read(), m = read(); if(n > m) swap(n, m);
if(n == 1) printf("%d", Qpow(2, m));
else if(n == 2) printf("%d", Mul(12, Qpow(3, m - 2)));
else if(n == 3) printf("%d", Mul(112, Qpow(3, m - 3)));
else{
f.g[1][1] = 912, f.g[1][2] = 2688, f.g[1][3] = 32;
tr.g[1][1] = 2, tr.g[1][2] = 6, tr.g[1][3] = 0;
tr.g[2][1] = 2, tr.g[2][2] = 6, tr.g[2][3] = 0;
tr.g[3][1] = Mod - 2, tr.g[3][2] = Mod - 9, tr.g[3][3] = 2;
ans.g[1][1] = ans.g[2][2] = ans.g[3][3] = 1;
x = n - 4;
while(x){
if(x & 1) ans = mul(ans, tr);
tr = mul(tr, tr), x >>= 1;
}
f = mul(f, ans);
if(n == m) printf("%d", f.g[1][1]);
else printf("%d", Mul(f.g[1][2], Qpow(3, m - n - 1)));
}
return 0;
}
我们可以继续按照计数的套路考虑怎样的一个矩阵是合法的,我们之前已经发现了一个条件:所有对角线(下面默认从左下到右上)一定都是 \(111 \cdots 0000\) 排列,但是我们需要注意的是这是两条路径没有相交的情况,那么如果两条路径相交,接下来下和右的优先级实际上反了过来,那么剩下的子矩阵所有对角线都将会是 \(000 \cdots 1111\) 这样子的,因此剩下的子矩阵的所有对角线要么全为 \(0\) 要么全为 \(1\),于此同时如果两条路径能在某个点相交当且仅当这个点上面的点和左边的点权值相同,因此一张合法矩阵的充要条件如下:
\(1.\) 所有对角线都是先一段 \(1\) 再一段 \(0\).
\(2.\) 对于所有的 \(x, y, a_{x - 1, y} = a_{x, y - 1}\),满足 \((x, y)\) 为左上角 \((n, m)\) 为右上角的矩形中所有对角线的权值相同。
于是我们可以考虑如何快速判断一个矩形是否合法,首先我们每次枚举一定是满足第一个条件的,只需要考虑第二个条件即可,我们当然可以枚举每个点,然后暴力判断对角线,但这样是不优的,不难发现如果我们令 \(dp_{i, j}\) 为 \((i, j) \sim (n, m)\) 这个矩形是否合法,那么就会有转移 \(dp_{i, j} = dp_{i + 1, j} \& dp_{i, j + 1} \& (a_{i, k} = a_{i + 1, k - 1} (j + 1 \le k \le m))\),最右边的那个东西是可以提前预处理出来的,于是判断我们就能 \(O(nm)\) 做了,于是直接考虑从最后一个对角线往前填,沿途一路 \(check\) 过来,这样的复杂度就很优了,跑出 \((8, 8)\) 轻而易举。有了上面这个判定方法以后实际上我们可以直接状压,令 \(dp_{i, j, k}\) 为当前枚举到第 \(i\) 个对角线,当前放置了 \(j\) 个 \(1\),强制对角线相同的位置状态为 \(k\) 的方案数,暴力转移即可,复杂度 \(O(n ^ 3 m 2 ^ n)\) 有很多转移是可以省去的,这样这个 \(dp\) 就可以跑得飞快了,我们可以轻松地打出大量的表那么也就可以轻松发现规律了,因此如果这个题想到这里实际上已经做完了本题,但本题还有更巧妙的性质没有发现。
我们考虑第 \(3\) 条对角线,不难发现这一条对角线上一定存在两个相同的相邻的权值,那么我们就能约数掉大部分的情况了,首先我们可以证明一下上面打表得出的 \(f_{n, m} = 3 \times f_{n, m - 1}\) 的结论。(感谢 \(\rm xenonex\) 的图)
分几种情况讨论一下,如果第二条对角线两个权值相同:
首先红框内是受限制的矩形,可以考虑新增加一列来计算增加的贡献,假设现在新增加最后绿色所在一列,不难发现有部分会随着之前矩形而受限制(绿色部分)而右下角的那个格子可以随意选择 \(0 / 1\),而右上角的那个位置会受蓝色对角线的影响,蓝色对角线有 \(\frac{1}{2}\) 的情况都选择 \(0\),那么右上角一定也会选择 \(0\),有 \(\frac{1}{2}\) 的情况选择 \(1\),那么右上角可以随意选择 \(0 / 1\),因此这部分的增量为 \(2 \times \frac{1}{2} (1 + 2) = 3\)。
剩下的情况只需枚举第三条对角线的所有状态,证明方法和上面的证明方法类似。
实际上上面的证明方法也给了我们很好的做题思路,我们同样可以分类讨论算出答案。
- 第二条对角线两个权值相同:
第一条第二条对角线都有两种选择,从第三条对角线开始,因为框内选数被限制,对于小于 \(n\) 的每条对角线可以看作是三个点的对角线需要满足第一个条件,不难发现有 \(4\) 种填法,而对于大于 \(n\) 小于等于 \(m\) 的每条对角线均有 \(3\) 种填法,剩下的情况只有两种填法,第一条对角线和第二条对角线分别有 \(2\) 种填法,因此此部分答案为 \(2 \times 2 \times 4 ^ {n - 2} \times 3 ^ {m - n} \times 2 ^ {n - 1} = 2 ^ {3n - 3} \times 3 ^ {m - n}\)
- 第二条对角线两个权值不相同且第三条对角线三个权值相同:
和上面的情况同理,答案为 \(5 \times 4 ^ {n - 4} \times 3 ^ {m - n} \times 2 ^ {n - 1} = 2 ^ {3n - 9} \times 3 ^ {m - n} \times 5\).
- 第二条对角线两个权值不相同且第三条对角线为 \(1, 1, 0\)
不难发现这里统计答案的难点在于上方 \(2 \times m\) 的一个矩形,因为如果不约束这个矩形答案非常难统计,可以考虑令 \(dp_{i, 0 / 1}\) 为当前选到第 \(i\) 条对角线,当前上方的矩形有没有被约束的方案,于是就有转移
\]
\]
\]
\]
那么最终答案如下:
\]
\]
- 第二条对角线两个权值不相同且第三条对角线为 \(1, 0, 0\)
与上面的情况类似
最终可以使用矩阵快速幂即可做到 \(O(\log n + \log m)\).
#include<bits/stdc++.h>
using namespace std;
#define N 2000000 + 5
#define Mod 1000000007
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int n, m, ans, tmp, dp[N][2];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
return (a -= b) < 0 ? a + Mod : a;
}
int Mul(int a, int b){
return 1ll * a * b % Mod;
}
int Qpow(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = Mul(ans, a);
a = Mul(a, a), b >>= 1;
}
return ans;
}
int main(){
n = read(), m = read(); if(n > m) swap(n, m);
if(n == 1) printf("%d", Qpow(2, m));
else if(n == 2) printf("%d", Mul(12, Qpow(3, m - 2)));
else if(n == 3) printf("%d", Mul(112, Qpow(3, m - 3)));
else{
ans = Mul(Qpow(2, 3 * n - 3), Qpow(3, m - n));
ans = Inc(ans, Mul(Mul(Qpow(2, 3 * n - 9), Qpow(3, m - n)), 20));
dp[3][0] = 1;
rep(i, 4, m){
dp[i][0] = dp[i - 1][0];
if(i <= n) dp[i][1] = Mul(4, Inc(dp[i - 1][0], Inc(dp[i - 1][1], dp[i - 2][0])));
else{
dp[i][1] = Mul(3, Inc(dp[i - 1][0], dp[i - 1][1]));
if(i - 1 <= n) dp[i][1] = Inc(dp[i][1], Mul(4, dp[i - 2][0]));
else dp[i][1] = Inc(dp[i][1], Mul(dp[i - 2][0], 3));
}
}
tmp = Mul(3, dp[m][0]);
if(m <= n){
tmp = Inc(tmp, Mul(dp[m - 1][0], 12));
tmp = Inc(tmp, Mul(Dec(dp[m][1], Mul(dp[m - 1][0], 4)), 2));
}
else{
tmp = Inc(tmp, Mul(dp[m - 1][0], 9));
tmp = Inc(tmp, Mul(Dec(dp[m][1], Mul(dp[m - 1][0], 3)), 2));
}
if(m <= n){
tmp = Mul(tmp, Qpow(2, n));
ans = Inc(ans, tmp);
printf("%d", ans);
return 0;
}
tmp = Mul(tmp, Qpow(2, n - 1));
ans = Inc(ans, tmp);
memset(dp, 0, sizeof(dp));
dp[3][0] = 1;
rep(i, 4, n){
dp[i][0] = dp[i - 1][0];
dp[i][1] = Mul(4, Inc(dp[i - 1][0], Inc(dp[i - 1][1], dp[i - 2][0])));
}
tmp = Mul(4, dp[n][0]);
tmp = Inc(tmp, Mul(dp[n - 1][0], 16));
tmp = Inc(tmp, Mul(Dec(dp[n][1], Mul(dp[n - 1][0], 4)), 3));
tmp = Mul(tmp, Qpow(3, m - n - 1));
tmp = Mul(tmp, Qpow(2, n));
ans = Inc(ans, tmp);
printf("%d", ans);
}
return 0;
}
NOIP2018 Day2T2 填数游戏的更多相关文章
- @NOIP2018 - D2T2@ 填数游戏
目录 @题目描述@ @题解@ @代码@ @题目描述@ 小 D 特别喜欢玩游戏.这一天,他在玩一款填数游戏. 这个填数游戏的棋盘是一个 n×m 的矩形表格.玩家需要在表格的每个格子中填入一个数字(数字 ...
- UOJ#440. 【NOIP2018】填数游戏 动态规划
原文链接www.cnblogs.com/zhouzhendong/p/UOJ440.html 前言 菜鸡选手到省选了才做联赛题. 题解 首先我们分析一下性质: 1. 假如一个格子是 0,那么它的右上角 ...
- 【逆向笔记】2017年全国大学生信息安全竞赛 Reverse 填数游戏
2017年全国大学生信息安全竞赛 Reverse 填数游戏 起因是吾爱破解大手发的解题思路,觉得题挺有意思的,就找来学习学习 这是i春秋的下载链接 http://static2.ichunqiu.co ...
- [Noip2018]填数游戏
传送门 Description 耳熟能详,就不多说了 Solution 对于一个不会推式子的蒟蒻,如何在考场优雅地通过此题 手玩样例,发现对于 \(n=1\) , \(ans=2^m\) .对于 \( ...
- NOIP2018 填数游戏 搜索、DP
LOJ 感觉这个题十分好玩于是诈尸更博.一年之前的做题心得只有这道题还记得清楚-- 设输入为\(n,m\)时的答案为\(f(n,m)\),首先\(f(n,m)=f(m,n)\)所以接下来默认\(n \ ...
- 【题解】NOIP2018 填数游戏
题目戳我 \(\text{Solution:}\) 题目标签是\(dp,\)但是纯暴力打表找规律可以有\(65\)分. 首先是对于\(O(2^{nm}*nm)\)的暴力搜索,显然都会. 考虑几条性质: ...
- luogu P5023 填数游戏
luogu loj 被这道题送退役了 题是挺有趣的,然而可能讨论比较麻烦,肝了2h 又自闭了,鉴于CSP在即,就只能先写个打表题解了 下面令\(n<m\),首先\(n=1\)时答案为\(2^m\ ...
- JZOJ5965【NOIP2018提高组D2T2】填数游戏
题目 作为NOIP2018的题目,我觉得不需要把题目贴出来了. 大意就是,在一个n∗mn*mn∗m的010101矩阵中,从左上角到右下角的路径中,对于任意的两条,上面的那条小于下面的那条.问满足这样的 ...
- [NOIP2018 TG D2T2]填数游戏
题目大意:$NOIP2018\;TG\;D2T2$ 题解:在skip2004的博客基础上修改的,也是暴搜. 说明一下把vector改成数组并不可以通过此题,记录. 结论:在$m>n+1$时答案为 ...
随机推荐
- Improving Adversarial Robustness via Channel-Wise Activation Suppressing
目录 概 主要内容 代码 Bai Y., Zeng Y., Jiang Y., Xia S., Ma X., Wang Y. Improving adversarial robustness via ...
- 使用pypy3加速python运行
从这里下载对应OS版本的安装包 解压: tar xf pypy-x.y.z.tar.bz2 然后通过./pypy-x.y.z/bin/pypy可以直接进入console 可以使用pip安装包: ./p ...
- 编写Java程序,实现客户端向服务端上传文件的功能
查看本章节 查看作业目录 需求说明: 实现客户端向服务端上传文件的功能 当启动服务端后,运行客户端程序,系统提示客户在客户端输入上传文件的完整路径.当客户在客户端输入完成后,服务端实现文件上传 实现思 ...
- Ranger-AdminServer安装(开启Kerberos)
Ranger-AdminServer安装, 同时开启Kerberos安全模式, 基于ranger版本0.7.0. 安装规划 10.43.159.240 zdh-240 10.43.159.245 zd ...
- xml文件 加载properties文件的两种方法与注意事项
1.遇到的问题: 配置redisSpringContext.xml 时,遇到 properties加载失败,提示BeanDefinitionStoreException 和 java.lang. ...
- Thrift框架-安装
1.前言 今天接触了使用 PRC[远程过程调用协议]的Thrift 框架 ,留下随笔心得,这是安装篇 2.下载 去apache官网下载Thrift脚本编译程序,window则下载一个exe文件,然后 ...
- vscode 前端好用插件汇总
本篇文章根据实际开发中使用的扩展插件,结合<精选!15 个必备的 VSCode 插件(前端类)>.<vscode 插件推荐 - 献给所有前端工程师(2017.8.18更新)>中 ...
- layui父表单获取子表单的值完成修改操作
最近在做项目时,学着用layui开发后台管理系统. 但在做编辑表单时遇到了一个坑. 点击编辑时会出现一个弹窗. 我们需要从父表单传值给子表单.content是传值给子表单 layer.open({ t ...
- 【基因组学】maker的安装和注释
本文默认读者有一定的生信基础,没有基础的可以阅读以前的笔记内容. maker作为比较受人认可的基因组注释软件,其流程较为清晰简单. 不知何故,我的conda无法安装maker,故而采用手动安装方式. ...
- LG1290 欧几里德的游戏
https://www.luogu.com.cn/problem/P1290 博弈论游戏,用到mod. 辗转相除法的过程,会构成n种状态. 到达最后一个状态就赢了. 对于一次过程如果div>1那 ...