【题解】与多米诺有关的插头DP

这篇博客介绍了如何解决多米诺骨牌覆盖棋盘的问题,其中涉及到了动态规划(DP)的应用。题目来自hdu5731和bzoj 1435,要求在特定条件下计算棋盘的稳定覆盖方案数。作者阐述了如何计算在不同列分割线状态下的方案数,并利用容斥原理得到最终答案。在处理bzoj 1435时,由于存在障碍,需要记录子矩阵的覆盖方案数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hdu5731

【题意】
给你一个nm的棋盘,
我们用1
2或2*1(即横着放或竖着放)的骨牌去填充这个棋盘。
问你有多少种填法,使得这个棋盘——
任意相邻两行或两列之间都必须要有一个骨牌横跨。
也就是使得这个棋盘为稳定的。

题解from phile的空间

先求出不考虑分割线的n*m棋盘的覆盖方案数记为f[n][m]
然后枚举列分割线的状态(状压),计算此时不存在行分割线的方案数
求出这个我们就可以用容斥原理算出答案了
怎么算在列分割线确定的情况下,不存在行分割线的方案数呢?
记s[i]=f[i][a1]f[i][a2]…表示在有i行不考虑行分割线且列分割线分成a1,a2…ak块的方案数
则到第i行不存在行分割线的方案数g[i]=s[i]-∑ g[j]*s[i-j] (1<=j<i)
相当于补集转化,求第一条行分割线为j时不合法的方案数……
一开始把答案都预处理出来即可

通过在第一个出现不合法位置来减去不合法,达到不重不漏的目的。是很常用的方法。
本题对列容斥,而对行用这种方法直接算出准确值

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const ld inf = 2e18;
const int maxn = 100020;
const ll mod = 1e9 + 7;

ll z[20],ans[20][20],g[20],f[20][20],a[20];

unordered_map <int,ll> h[2];
int now,last;

inline void add(ll &x,ll y){
	x += y;
	if (x >= mod) x-= mod;
	if ( x < 0 ) x += mod;
}
void calc_f(){
	for (int n = 1 ; n <= 16 ; n++){
		h[0].clear() , h[1].clear();
		now = 0; h[now][0] = 1;
		for (int i = 1 ; i <= 16 ; i++){
			for (int j = 1 ; j <= n ; j++){
				last = now , now ^= 1;
				h[now].clear();
				if ( j == 1 ) f[n][i - 1] = h[last][0];
				for (const auto &it : h[last]){
					int s = it.fi; ll d = it.se;
					if ( j == 1 ){
						if ( (s >> n) & 1 ) continue;
						s <<= 1;
					}
					int up = (s >> (j - 1)) & 1 , left = (s >> j) & 1; 
					int cur = s - (up << (j - 1)) - (left << j);
					if ( up && left ) continue;
					if ( up || left ){
						add(h[now][cur],d);
					}
					else{ //create
						add(h[now][cur + (1 << (j - 1))],d);
						add(h[now][cur + (1 << j)],d);
					}
				}
			}
		}
		f[n][16] = h[now][0];
	}
	//for (int i = 1 ; i <= 16 ; i++){ for (int j = 1 ; j <= 16 ; j++){ cout<<f[i][j]<<" "; } cout<<endl; }
}
int split(int s,int m){
	int k = 0,last = -1;
	for (int i = 0 ; i < m - 1 ; i++){
		if ( s & (1 << i) ){
			a[++k] = i - last;
			last = i;
		}
	}
	a[++k] = m - last - 1;
	return k;
}
void calc_ans(){
	for (int m = 1 ; m <= 16 ; m++){
		int S = (1 << (m - 1)) - 1;
		for (int s = 0 ; s <= S ; s++){
			int k = split(s,m);
			for (int n = 1 ; n <= 16 ; n++){
				z[n] = 1;
				for (int i = 1 ; i <= k ; i++) z[n] = z[n] * f[n][a[i]] % mod;
				g[n] = z[n];
				for (int i = 1 ; i < n ; i++) g[n] = (g[n] - g[i] * z[n - i] % mod + mod) % mod;
				add(ans[n][m],g[n] * (!(k & 1) ? (-1) : 1));
			}
		}
	}
}
void init(){
	calc_f();
	calc_ans();
}

int main(){
	init();
	int n,m;
	while ( ~scanf("%d %d",&n,&m) ) printf("%lld\n",ans[n][m]);
}
bzoj 1435: [ZJOI2009]多米诺骨牌

本题只是多了障碍的限制,使得需要记录任意子矩阵的覆盖方案数。

bzoj竟然连unordered_map都不支持,编译器真的太老了, 本地过了对拍。懒得写hashmap

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const ld inf = 2e18;
const int maxn = 100020;
const int mod = 19901013;

int z[20][20],ans[20][20],g[20],f[20][20][20][20],a[20];
char ch[20][20];
unordered_map <int,int> h[2];
unordered_map <int,int>::iterator it;
int now,last,N,M;

inline void add(int &x,int y){
	x += y;
	if (x >= mod) x-= mod;
	if ( x < 0 ) x += mod;
}
void calc_f(){
	for (int l = 1 ; l <= N ; l++) for (int r = l ; r <= N ; r++){
		int n = (r - l + 1);
		
		for (int i = 1 ; i <= M ; i++){
			h[0].clear() , h[1].clear();
			now = 0; h[now][0] = 1;
		for (int k = i ; k <= M ; k++){
			for (int j = l ; j <= r ; j++){
				last = now , now ^= 1;
				h[now].clear();
				if ( j == l ) f[l][r][i][k - 1] = h[last][0];
				for (it = h[last].begin() ; it != h[last].end() ; ++it){
					int s = (*it).fi; int d = (*it).se;
					if ( j == l ){
						if ( (s >> n) & 1 ) continue;
						s <<= 1;
					}
					int up = (s >> (j - l)) & 1 , left = (s >> (j - l + 1)) & 1; 
					int cur = s - (up << (j - l)) - (left << (j - l + 1));
					if ( up && left ) continue;
					if ( up || left ){
						if ( ch[j][k] == '.' ) add(h[now][cur],d);
					}
					else{ //create
						if ( ch[j][k] == '.' ){
							add(h[now][cur + (1 << (j - l))],d);
							add(h[now][cur + (1 << (j - l + 1))],d);
						}
						add(h[now][cur],d);
					}
				}
			}
		}
		f[l][r][i][M] = h[now][0];
		}
	}
	//for (int i = 1 ; i <= N ; i++){ for (int j = 1 ; j <= M ; j++){ cout<<f[i][j]<<" "; } cout<<endl; }
}
int split(int s,int m){
	int k = 0,last = -1;
	for (int i = 0 ; i < m - 1 ; i++){
		if ( s & (1 << i) ){
			a[++k] = i - last;
			last = i;
		}
	}
	a[++k] = m - last - 1;
	return k;
}
void calc_ans(){
		int m = M;
		int S = (1 << (m - 1)) - 1;
		for (int s = 0 ; s <= S ; s++){
			int k = split(s,m);
		//	cout<<"split\n";
		//	rep(i,1,k) cout<<a[i]<<" ";
		//	cout<<endl;
			for (int n = 1 ; n <= N ; n++){
				//z[n] = 1;
				for (int l = 1 ; l <= n ; l++){
					z[l][n] = 1;
					int cur = 1;
					for (int i = 1 ; i <= k ; i++) z[l][n] = (ll)z[l][n] * f[l][n][cur][cur + a[i] - 1] % mod , cur += a[i];
				}
				g[n] = z[1][n];
				for (int i = 1 ; i < n ; i++) g[n] = (g[n] - (ll)g[i] * z[i + 1][n] % mod + mod) % mod;
				add(ans[n][m],g[n] * (!(k & 1) ? (-1) : 1));
			}
		//	for (int i = 1 ; i <= N ; i++){
		//		cout<<i<<" g : "<<g[i]<<endl;
		//		for (int j = 1 ; j <= i ; j++) cout<<z[i][j]<<" ";
		//		cout<<endl;
		//	}
		}

}
void init(){
	calc_f();
	calc_ans();
}

int main(){
	freopen("input.txt","r",stdin);
	scanf("%d %d",&N,&M);
	for (int i = 1 ; i <= N ; i++) scanf("%s",ch[i] + 1);
	init();
	printf("%d\n",ans[N][M]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值