多回路问题

/*
//多回路问题,升级版为单回路(因为要考虑连通分量的合并),可以看做例题
#include<iostream>
#include<cstring>

using namespace std;

using LL=long long;
int n,m;
LL dp[2][1<<13];

int main(){
	cin.tie(nullptr)->sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--){
		memset(dp,0,sizeof dp);
		cin>>n>>m;
		int pre=0,now=1;//滚动数组优化
		dp[pre][0]=1;//边界条件,显然这时肯定构成回路,并且显然不能有插头
		for(int i=0;i<n;i++){//有的是正序,有的是倒序,有的两种皆可,不清楚
			for(int j=0;j<m;j++){
				for(int S=0;S<1<<(m+1);S++){
					//<2^(m+1)? 因为有m个上(下)插头,1个左插头
					int tmp;
					cin>>tmp;
					//注意!!!!!!!!!(本人感觉这是插头DP的重点)每次考虑当前方格下方和右方的插头,看从左方和上方的什么状态转移过来
					int sl=S&(1<<(j+1)),su=S&(1<<j);//左边(列从右到左数)和上方的状态
					if(!tmp){//存在障碍物,不能有任何插头
						if(!sl && !su)
							dp[now][S]+=dp[pre][S];
					}
					else{
						if(sl^su){//左、上只有一个插头,那么右、下有一个即可
							dp[now][S]+=dp[pre][S];//原状态不变(即不留插头)
							dp[now][S^(1<<j)^(1<<(j+1))]+=dp[pre][S];//原状态改变,即留下插头
						}
						else{//即都有、都没有插头,那么只能改变原状态,否则不合法(可以画一下图)
							dp[now][S^(1<<j)^(1<<(j+1))]+=dp[pre][S];
						}
					}
				}
				swap(pre,now);//处理完一个方格就交换
			}
			memset(dp[now],0,sizeof dp[now]);
			for(int S=0;S<(1<<(m+1))>>1;S++) dp[now][S<<1]=dp[pre][S];
			// 在两行交界处,左插头由最右边到最左边(最高位到最低位,除最后一位变为0其余状态不变),而最左边应为空,因此状态整体左移一位
			swap(pre,now);
		}
		cout<<dp[now][0]<<'\n';
	}
}
*/
#include<bits/stdc++.h>
using namespace std;
const int max_s = 1 << 12 | 5;
long long f[2][max_s],*f0,*f1; // 指针 f0,f1:表示当前从 f0 转移到 f1
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, m;
		scanf("%d%d", &n, &m);
		f0 = f[0], f1 = f[1];
		int lim = 1 << m + 1; // m 个上插头,1 个左插头
		fill(f1, f1 + lim, 0); // 初始时记得清空
		f1[0] = 1; // 边界不能有插头
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < m; ++j) { // 为方便状压,下标从 0 开始
				swap(f0, f1); // 上一次的计算结果存储于 f1 中,转移到 f0 中
				fill(f1, f1 + lim, 0); // 并清空 f1
				int mark;
				scanf("%d", &mark);
				// s 第 j 位由 f0 的 (i,j) 左边界到 f1 的 (i,j) 下边界
				// s 第 j+1 位由 f0 的 (i,j) 上边界到 f1 的 (i,j) 右边界
				for (int s = 0; s < lim; ++s) {
					long long v = f0[s];
					if (v) {
						int l = s >> j & 1, u = s >> j + 1 & 1;
						if (mark) {
							// 要形成回路,每个格子应恰有 2 个插头
							if (l == u) // 若左、上都有插头或都没有插头,则下、右都没有插头或都有插头
								f1[s ^ 3 << j] += v; // s^3<<j 相当于 s^1<<j^1<<j+1
							else // 若左、上恰有 1 个插头,则剩余插头可以向右,也可以向下
								f1[s ^ 3 << j] += v, f1[s] += v; // 即可以都取反,也可以都不取反
						} else {
							if (!l && !u)
								f1[s] += v; // 若有障碍物,则不能有任何一个插头
						}
					}
				}
			}
			swap(f0, f1);
			fill(f1, f1 + lim, 0);
			for (int s = 0; s < (1 << m); ++s)
				f1[s << 1] = f0[s];
			// 在两行交界处,左插头由最右边到最左边(最高位到最低位),而最左边应为空,因此状态整体左移一位
		}
		printf("%lld\n", f1[0]); // 边界不能有插头
	}
	return 0;
}

posted on 2026-02-02 21:29  _CENSORED  阅读(0)  评论(0)    收藏  举报

导航