P8933 技巧性的块速递推(dfs)

题目:

题目背景

充分必要,切比雪夫。

原来还是,不需要了。

题目描述

一个 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 数组里面,剩下的就自己发挥,可以直接用它预处理。

  1. 搜索的简单框架还是很好想的:每次搜一个点,枚举是黑还是白,然后接着搜下一个点,整个棋盘搜索完之后check一下,符合的话方案数就加 1。

  2. 接下来还要加一个剪枝:由于上面推出的第二个条件,所以当一个点的横坐标 ≥4≥4 时(即存在 (i−3,j),就可以直接根据 (i−3,j) 到 (i,j) 间的点求出 (i,j) 点的颜色,没必要再枚举。

  3. 不过不能问一次搜一次,因为 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 
*/

制作不易,点个赞吧,球球啦(✪ω✪)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值