题目:
题目背景
充分必要,切比雪夫。
原来还是,不需要了。
题目描述
一个 n×m 的棋盘,对每个格子染上黑白两色之一。
询问有多少种染色方式,使得不存在横、竖、斜连续四个格子中存在至少三个相同颜色的格子,并且不存在横、竖、斜连续三个格子的颜色相同。
若设棋盘的左上角为(1,1),右下角为(n,m),则称 {(x,y),(x+1,y),(x+2,y)}为横的连续三个格子,{(x,y),(x,y+1),(x,y+2)} 为竖的连续三个格子、{(x,y),(x+1,y+1),(x+2,y+2)} 和{(x,y),(x+1,y−1),(x+2,y−2)} 为斜的连续三个格子(以上格子均在棋盘内)。
连续四个格子同理。
输入格式
本题有多组数据。
第一行一个整数 T 表示数据组数。
接下来 T 行,每行两个整数 n,m 表示一次询问。
输出格式
共 T 行,每行一个整数表示答案。答案对 998244353998244353 取模。
输入输出样例
输入 #1
1 2 2
输出 #1
16
输入 #2
1 3 3
输出 #2
32
说明/提示
样例解释
样例 1:显然任意染色均合法,答案为 24=1624=16。
样例 2:
101
110
010
这是合法方案之一。
111
110
011
这是不合法方案之一,因为 {(1,1),(1,2),(1,3)}、{(1,2),(2,2),(3,2)} 和 {(1,1),(2,2),(3,3)} 均不满足条件。
数据规模
对于 100% 的数据,1≤T≤10^5,1≤n,m≤10^9。
思路:
假设 1 表示黑棋, 0 表示白棋。
首先,不考虑斜向的情况,如果一个格子在左边或上面存在 3 个已确定的格子,则当前的格子是确定的,如下图:
同理,我们只需要确定一个 3×3 的区域,其他向外延伸格子都是确定的。
在向外的扩展中,也有可能在斜向上出现问题,导致一些原本的合法解被排除,如图:
这个 3×3 目前是合法的,但是在向外扩展的时候会变得不合法:
这样在斜向就出问题了。
于是我们可以得出一个结论:在n,m≥3 的棋盘中,最多不会超过 32 种解(样例给的),模数是虚假的
而且,对于棋盘任意一个位置 a[i][j] (i,j≥5) 它必定等于左边 3 个位置只有一个的 a ,a∈{0,1} 。同理,对于 a[i][j]−4 ,它也等于右边 3 个位置只有一个的 a 。这两个 a 是完全相同的,所以 a[i][j]=a[i][j]−4 ,棋盘以 4×4 为一个周期循环。
而斜面上由于循环,所以我们只用考虑与左上角的 4×4 有关系的斜向线段,最多可以到 (4,4),(5,5),(6,6),(7,7) 有问题,那么只用弄出前 7×7 的方案数,则 n,m≥7 的情况就等于 7×7 的方案数了。(一个出问题的斜向线段必定可以包含在一个 4×4 的棋盘中,而 7×7 枚举了循环中的所有可能出现的 4×4 小区域,故使用 7×7 )。
对于 n,m 其中一个小于 7 的情况,只用搜出 1×1, 1×2, ⋯ , 1×7, 2×2 ⋯ , 2×7⋯ , 3×3⋯ , 3×7⋯ , 7×7 特判就好了,因为 2×1000 之类的方案数肯定等于 2×7 的。
下面用一个dfs预处理代码,来预处理出 1×1 到 7×7 棋盘的方案数的,结果存在 dp 数组里面,剩下的就自己发挥,可以直接用它预处理。
-
搜索的简单框架还是很好想的:每次搜一个点,枚举是黑还是白,然后接着搜下一个点,整个棋盘搜索完之后check一下,符合的话方案数就加 1。
-
接下来还要加一个剪枝:由于上面推出的第二个条件,所以当一个点的横坐标 ≥4≥4 时(即存在 (i−3,j),就可以直接根据 (i−3,j) 到 (i,j) 间的点求出 (i,j) 点的颜色,没必要再枚举。
-
不过不能问一次搜一次,因为 T≤10^5,会时超。可以先预处理 7×7 范围所有大小的方阵答案,询问时直接输出,就不会时超了。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,ans,a[2001][2001],dp[2001][2001];
inline bool check(){//判断合不合法
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(i+2<=n/*横向3个颜色不一样*/&&a[i][j]==a[i+1][j]&&a[i][j]==a[i+2][j]) return 0;
if(j+2<=m/*竖向3个颜色不一样*/&&a[i][j]==a[i][j+1]&&a[i][j]==a[i][j+2]) return 0;
if(i+2<=n&&j+2<=m/*斜线3个颜色不一样*/&&a[i][j]==a[i+1][j+1]&&a[i][j]==a[i+2][j+2]) return 0;
if(i-2>=1&&j+2<=m/*斜线3个颜色不一样*/&&a[i][j]==a[i-1][j+1]&&a[i][j]==a[i-2][j+2]) return 0;
if(i+3<=n){//横向4个不能有3个一样
int sum1=0,sum2=0;//黑与白的个数
for(int k=i;k<=i+3;++k){
if(a[k][j]) sum1++;
else sum2++;
}
if(sum1>=3||sum2>=3) return 0;
}
if(j+3<=m){//竖向4个不能有3个一样
int sum1=0,sum2=0;//黑与白的个数
for(int k=j;k<=j+3;++k){
if(a[i][k]) sum1++;
else sum2++;
}
if(sum1>=3||sum2>=3) return 0;
}
if(i+3<=n&&j+3<=m){//斜向4个不能有3个一样
int sum1=0,sum2=0;//黑与白的个数
for(int k=0;k<=3;++k){
if(a[i+k][j+k]) sum1++;
else sum2++;
}
if(sum1>=3||sum2>=3) return 0;
}
if(i-3>=1&&j+3<=m){//斜向4个不能有3个一样
int sum1=0,sum2=0;//黑与白的个数
for(int k=0;k<=3;++k){
if(a[i-k][j+k]) sum1++;
else sum2++;
}
if(sum1>=3||sum2>=3) return 0;
}
}
}
return 1;//合法
}
inline void dfs(int x,int y){//x和y表示当前的点
if(y>m){//搜完换行
y=1;
x++;
}
if(x>n){//全搜完了
if(check()) ans++;
return ;
}
if(y>=4){//剪枝
int sum1=0,sum2=0;
for(int i=y-3;i<=y-1;++i){//统计颜色
if(a[x][i]) sum1++;
else sum2++;
}
if(!sum1||!sum2) return ;//如果不合法直接return
if(sum1==1) a[x][y]=1;
if(sum2==1) a[x][y]=0;//取少的作为当前点的颜色
dfs(x,y+1);//继续搜
return ;
}
a[x][y]=1;//涂成黑色
dfs(x,y+1);//搜索
a[x][y]=0;//涂成白色
dfs(x,y+1);//搜索
}
int main(){
for(int i=1;i<=7;++i){
for(int j=1;j<=7;++j){
n=i;
m=j;
ans=0;
dfs(1,1);
dp[n][m]=ans;
}
}//预处理记录
int T;
cin>>T;
while(T--){//T组数据
cin>>n>>m;
if(n>7) n=7;
if(m>7) m=7;//>7时转化为7
cout<<dp[n][m]<<endl;
}
return 0;
}
/*
1
2 2
16
—————————————————————————
1
3 3
32
*/
制作不易,点个赞吧,球球啦(✪ω✪)