Luogu P2595 [ZJOI2009]多米诺骨牌 容斥,枚举,插头dp,轮廓线dp

真的是个好(毒)题(瘤)。其中枚举的思想尤其值得借鉴。

(40pts):插头(dp),记录插头的同时记录每一列的连接状况,复杂度(O(N*M*2^{n + m} ))

(100pts):容斥(+)插头(/)轮廓线。目前要维护每两行和每两列的限制,我们把两个限制分开讨论。预处理一下每个子矩阵如果不作限制的可行方案,然后人为地进行限制分割。对于列的限制用容斥解决,对于行的限制套在里面逐行枚举做一次(dp),就可以把复杂度降到可以接受的(O(m^3*2^n))

代码有借鉴网上。并非完全原创。

(40pts)

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

const int N = 15 + 5;
const int base = 2999830;
const int Mod = 19901013;
const int M = 3000000 + 5;
typedef unsigned int uint;

int n, m, cur, las, cnt[2], can[N][N];

int nxt[M], head[M], dp[2][M]; uint Hash[2][M];

int get_wei (uint zt, int wei) {
	return (zt >> wei) % 2;
}

int alt_wei (uint zt, int wei, int val) {
	return zt + ((0ll + val - get_wei (zt, wei)) << wei);
}

void update (uint zt, int val) {
	uint _zt = zt % base;
	for (int i = head[_zt]; i; i = nxt[i]) {
		if (Hash[cur][i] == zt) {
			(dp[cur][i] += val) %= Mod; return;
		}
	}
	nxt[++cnt[cur]] = head[_zt];
	head[_zt] = cnt[cur];
	Hash[cur][cnt[cur]] = zt;
//	cout << "dp[" << cur << "][" << cnt[cur] << "] = " << val << endl;
	dp[cur][cnt[cur]] = val;
} 

int get_val (uint zt) {
	uint _zt = zt % base;
	for (int i = head[_zt]; i; i = nxt[i]) {
		if (Hash[cur][i] == zt) {
			return dp[cur][i];
		}
	}
	return 0;
}

void change_row () {
	for (int i = 1; i <= cnt[cur]; ++i) {
		uint &zt = Hash[cur][i];
		if (get_wei (zt, m + m) == 0) {
			dp[cur][i] = 0;//到不了下一行 
		} 
		for (int i = m; i >= 1; --i) {
			zt = alt_wei (zt, i, get_wei (zt, i - 1));
		}
		zt = alt_wei (zt, 0, 0); // 全都向后移一位 
		zt = alt_wei (zt, m + m, 0); // 到下一行的标记清空 
	} 
}

int solve () {
	update (1 << (m + m), 1);
	for (int i = 1; i <= n; ++i) {
		change_row ();
		for (int j = 1; j <= m; ++j) {
//			cout << "i = " << i << " j = " << j << endl;
			las = cur, cur ^= 1, cnt[cur] = 0;
			memset (head, 0, sizeof (head));
			for (int k = 1; k <= cnt[las]; ++k) {
				uint zt = Hash[las][k];
				int b1 = get_wei (zt, j - 1);
				int b2 = get_wei (zt, j - 0);
				int val = dp[las][k];
				if (val == 0) continue; // 不转移的小优化 
//				cout << "b1 = " << b1 << " b2 = " << b2 << endl;
				if (!can[i][j]) {
					if (!b1 && !b2) {
						update (zt, val);
					}
				} else {
					if (b1 == 0 && b2 == 0) {
						if (can[i][j + 1]) {
							uint _zt = zt;
							_zt = alt_wei (_zt, j - 0, 1); // 右插头改为 1 
							_zt = alt_wei (_zt, m + j, 1); // j 列和 j + 1 列相连
							update (_zt, val); 
						}
						if (can[i + 1][j]) {
							uint _zt = zt;
							_zt = alt_wei (_zt, j - 1, 1); // 下插头改为 1
							_zt = alt_wei (_zt, m + m, 1); // 和下一行连通 
							update (_zt, val);
						}
						update (zt, val); // 注意你并不需要放满所有没有障碍的格子
					}
					if (b1 + b2 == 1) { // 有一个连入的插头 
						uint _zt = zt;
						_zt = alt_wei (_zt, j - 0, 0);
						_zt = alt_wei (_zt, j - 1, 0);
						update (_zt, val);
					}
				}
			}
//			cout << "las : " << endl;
//			for (int k = 1; k <= cnt[las]; ++k) {
//				cout << "state = " << Hash[las][k] << " val = " << dp[las][k] << endl;
//			}
//			cout << "cur : " << endl;
//			for (int k = 1; k <= cnt[cur]; ++k) {
//				cout << "state = " << Hash[cur][k] << " val = " << dp[cur][k] << endl;
//			}
//			cout << endl;
		}
	}
	uint fin = 0;
	for (int i = m + 1; i < m + m; ++i) {
		fin |= (1 << i); 
	} 
	return get_val (fin);
}

int main () {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			int ch = getchar ();
			while (ch != '.' && ch != 'x') {
				ch = getchar ();
			}
			if (ch == '.') {
				can[i][j] = true;
			}
		}
	}
//	for (int i = 1; i <= n; ++i) {
//		for (int j = 1; j <= m; ++j) {
//			cout << can[i][j] << " ";
//		}
//		cout << endl;
//	}
	cout << solve () << endl;
}

(100pts)

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

const int N = 15 + 5;
const int M = 40000 + 5;
const int Mod = 19901013;

int n, m, top, ans; 
int f[N], num[N], tmp[2][M], dp[N][N][N][N];

bool mp[N][N];

int ask (int u, int v) {
	return (u >> v) % 2;
}

void work (int w, int u, int v) {
	int las = 0, tot = (1 << (v - u + 1)) - 1;
	for (int i = 0; i <= tot; ++i) {
		tmp[0][i] = 0;
	}
	int t2 = 0;
	for (int i = u; i <= v; ++i) {
		if (mp[w][i]) {
			t2 |= (1 << (i - u));
		} 
	}
	tmp[0][t2] = 1;
	for (int i = w + 1; i <= m + 1; ++i) {
		int zt = 0;
		for (int j = u, t = 0; j <= v; ++j, ++t) {
			zt |= (mp[i][j] << t);
			int cur = las ^ 1;
			for (int k = 0; k <= tot; ++k) tmp[cur][k] = 0;
			for (int k = 0; k <= tot; ++k) {
				if(!tmp[las][k]) continue;
				int t2 = k;
				if (ask(t2,t) != mp[i][j]) {
					t2 ^= (1 << t);
				}
				(tmp[cur][t2] += tmp[las][k]) %= Mod;
				if (ask (k, t)) continue;
				if (j != v && !ask (k, t + 1)) {
					t2 = k | (1 << (t + 1));
					if (ask (t2, t) != mp[i][j]) {
						t2 ^= (1 << t);
					}
					(tmp[cur][t2] += tmp[las][k]) %= Mod;
				}
				if(!mp[i][j]) {
					t2 = k | (1 << t);
					(tmp[cur][t2] += tmp[las][k]) %= Mod;
				}
			}
			las = cur;
		}
		dp[w][u][i - 1][v] = tmp[las][zt];
	}
}

int main () {
	cin >> m >> n;
	for(int i = 1; i <= m; ++i) {
		for (int j = 1; j <= n; ++j) {
			int ch = getchar ();
			while (ch != '.' && ch != 'x') {
				ch = getchar ();
			}
			mp[i][j] = (ch == 'x');
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = i; j <= n; ++j) {
			for (int k = 1; k <= m; ++k) {
				work (k, i, j);
			}
		}
	}
	
	int tot = (1 << (n - 1)) - 1;
	for(int i = 0; i <= tot; ++i) {
		int top = 1;
		num[top] = 0;
		for (int j = 0; j < n - 1; ++j) {
			if (ask (i, j)) num[++top] = j + 1;
		}
		num[++top] = n;
		for (int d = 1; d <= m; ++d) {
			for (int u = 1; u <= d; ++u) {
				int t = 1;
				for(int j = 2; j <= top; ++j) {
					int p = num[j - 1] + 1, q = num[j];
					t = (1ll * t * dp[u][p][d][q]) % Mod;
				}
				if (u == 1) {
					f[d] = t;
				} else {
					f[d] = (0ll + f[d] - (1ll * t * f[u - 1])) % Mod;
				}
			}
		}
		(f[m] += Mod) %= Mod; 
		if (top % 2 == 1) {
			(ans -= f[m]) %= Mod;
		} else { 
			(ans += f[m]) %= Mod;
		}
	}
	cout << (ans + Mod) % Mod << endl;
}
原文地址:https://www.cnblogs.com/maomao9173/p/10832633.html