【YBT2022寒假Day1 B】方格填写(插头DP)

方格填写

题目链接:YBT2022寒假Day1 B

题目大意

有一个二维网格,然后里面每个位置要放一个 0~4 的数有一些已经填好的,也有要你填的。
然后一个位置可以跟相邻的四个点连边,然后要求一个点的边数量等于填的数字。
然后问你所有填写方案边的匹配方案数的平方的和。

思路

考虑这个平方的意义。
它其实是可以变成你选任意一种填数方式,在这中间任选两个匹配方案的方案数。

那我们就可以状压,因为看到状态很小,我们可以这样插头 DP:
f i , j , k , l f_{i,j,k,l} fi,j,k,l 为当前搞到 ( i , j ) (i,j) (i,j) 的位置,两个方案的状态分别是 k , l k,l k,l。( k , l k,l k,l m + 1 m+1 m+1 位的状压,维护 m m m 个向下的,一个向右的)

然后转移一下即可,要注意的是要滚动数组。

代码

#include<cstdio>
#include<cstring>
#define ll long long
#define mo 998244353

using namespace std;

int T, n, m, a[71][7], aa[5], bb[5];
ll f[2][128][128];

int main() {
	freopen("grid.in", "r", stdin);
	freopen("grid.out", "w", stdout);
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &m);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				scanf("%d", &a[i][j]);
		
		memset(f, 0, sizeof(f));
		f[0][0][0] = 1;
		int now = 1;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++) {
				if (a[i][j] != -1) {
					for (int fr = 0; fr < (1 << (m + 1)); fr++) {
						for (int frr = 0; frr < (1 << (m + 1)); frr++) {
							int to = fr, too = frr;
							int num = a[i][j], numm = a[i][j];
							if (j == 1 && ((to >> m) & 1)) continue;
							if (j == 1 && ((too >> m) & 1)) continue;
							if ((to >> m) & 1) num--, to -= (1 << m);
							if ((to >> (j - 1)) & 1) num--, to -= (1 << (j - 1));
							if ((too >> m) & 1) numm--, too -= (1 << m);
							if ((too >> (j - 1)) & 1) numm--, too -= (1 << (j - 1));
							if (num < 0 || numm < 0 || num > 2 || numm > 2) continue;
							
							if (!f[now ^ 1][fr][frr]) continue;
							ll x = f[now ^ 1][fr][frr];
							if (num == 0) aa[0] = 1, aa[1] = 0;
								else if (num == 1) aa[0] = 2, aa[1] = (1 << m), aa[2] = (1 << (j - 1));
									else if (num == 2) aa[0] = 1, aa[1] = (1 << m) | (1 << (j - 1));
							if (numm == 0) bb[0] = 1, bb[1] = 0;
								else if (numm == 1) bb[0] = 2, bb[1] = (1 << m), bb[2] = (1 << (j - 1));
									else if (numm == 2) bb[0] = 1, bb[1] = (1 << m) | (1 << (j - 1));
							for (int ii = 1; ii <= aa[0]; ii++)
								for (int jj = 1; jj <= bb[0]; jj++)
									(f[now][to | aa[ii]][too | bb[jj]] += x) %= mo;
						} 
					}
				}
				else {
					for (int qq = 0; qq <= 4; qq++) {
						for (int fr = 0; fr < (1 << (m + 1)); fr++) {
							for (int frr = 0; frr < (1 << (m + 1)); frr++) {
								int to = fr, too = frr;
								int num = qq, numm = qq;
								if (j == 1 && ((to >> m) & 1)) continue;
								if (j == 1 && ((too >> m) & 1)) continue;
								if ((to >> m) & 1) num--, to -= (1 << m);
								if ((to >> (j - 1)) & 1) num--, to -= (1 << (j - 1));
								if ((too >> m) & 1) numm--, too -= (1 << m);
								if ((too >> (j - 1)) & 1) numm--, too -= (1 << (j - 1));
								if (num < 0 || numm < 0 || num > 2 || numm > 2) continue;
								
								if (!f[now ^ 1][fr][frr]) continue;
								ll x = f[now ^ 1][fr][frr];
								if (num == 0) aa[0] = 1, aa[1] = 0;
									else if (num == 1) aa[0] = 2, aa[1] = (1 << m), aa[2] = (1 << (j - 1));
										else if (num == 2) aa[0] = 1, aa[1] = (1 << m) | (1 << (j - 1));
								if (numm == 0) bb[0] = 1, bb[1] = 0;
									else if (numm == 1) bb[0] = 2, bb[1] = (1 << m), bb[2] = (1 << (j - 1));
										else if (numm == 2) bb[0] = 1, bb[1] = (1 << m) | (1 << (j - 1));
								for (int ii = 1; ii <= aa[0]; ii++)
									for (int jj = 1; jj <= bb[0]; jj++)
										(f[now][to | aa[ii]][too | bb[jj]] += x) %= mo;
							} 
						}
					}
				}
				
				now ^= 1;
				memset(f[now], 0, sizeof(f[now]));
			}
		
		printf("%lld\n", f[now ^ 1][0][0]);
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值