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$时答案为 ...
随机推荐
- 「AHOI2013」 差异
知识点: SA,线段树,单调栈 原题面 Loj Luogu 题意简述 给定一长度为 \(n\) 的字符串 \(S\),令 \(T_i\) 表示从第 \(i\) 个字符开始的后缀,求: \[\sum_{ ...
- Insights直播回顾——手语服务,助力沟通无障碍
HMS Core Insights第九期直播–手语服务,助力沟通无障碍,已于12月29日圆满结束,本期直播与小伙伴们一同了解了HMS Core手语服务的亮点特性.底层技术以及演进规划,下面我们一起来回 ...
- [opencv]二维码识别率提升方案-resize调整
这里采用循环resize的方式,对二维码图像进行放缩. 识别到name(二维码结果)不为空,则立即退出循环 //循环识别 for (int i = 1;name.empty(); i++){ resi ...
- spring练习,使用Eclipse搭建的Spring开发环境,属性注入通过构造方法方式实现,模拟用户的正常登录。
相关 知识 >>> 相关 练习 >>> 实现要求: 使用Eclipse搭建的Spring开发环境,属性注入通过构造方法方式实现,模拟用户的正常登录.要求如下: 通过 ...
- 云南农职《JavaScript交互式网页设计》 综合机试试卷②——实现轮播图效果
一.语言和环境 实现语言:HTML,CSS,JavaScript,JQuery. 开发环境:HBuilder. 二.题目(100分): 使用JQuery淡入淡出动画,实现轮播图效果 每隔2秒钟切换一张 ...
- Java高级程序设计笔记 • 【第2章 多线程(一)】
全部章节 >>>> 本章目录 2.1 线程的概述 2.1.1 进程 2.1.2 多线程优势 2.1.3 Thread 类 2.1.4 实践练习 2.2 Runnable接口 ...
- Python两处容易理解错误的设计
函数内部修改可变类型的变量时不会视作局部变量(除非函数内有该变量的赋值运算符),因为如果做局部变量处理则修改语句势必报错,此处的理解不会有歧义: s = 'test' d = {True:1,2:'S ...
- CSS基础-5 伪类
一.伪类 我们以a标签为例 伪类标签分为4类 1. 设置超链接默认的样式 a:link {属性:值;.....} 或者 a { 属性: 值;} 推荐使用这种方式 2 ...
- Django_模型类详解(七)
# 定义书籍模型类 class BookInfo(models.Model): btitle = models.CharField(max_length=20) # 书籍名称 bpub_date = ...
- 经典变长指令-RegOpcode
一.回顾Mod/M结构 Intel 64 and IA-32 Architectures Instruction Format ModR/M结构图 Mod与R/M共同描述E的意义(内存或者通用寄存器) ...