题目描述】 lxhgww的小名叫“小L”,这是因为他总是很喜欢L型的东西。小L家的客厅是一个的矩形,现在他想用L型的地板来铺满整个客厅,客厅里有些位置有柱子,不能铺地板。现在小L想知道,用L型的地板铺满整个客厅有多少种不同的方案? 需要注意的是,如下图所示,L型地板的两端长度可以任意变化,但不能长度为0。铺设完成后,客厅里面所有没有柱子的地方都必须铺上地板,但同一个地方不能被铺多次。
【输入】 输入的第一行包含两个整数,R和C,表示客厅的大小。 接着是R行,每行C个字符。’_’表示对应的位置是空的,必须铺地板;’*’表示对应的位置有柱子,不能铺地板。 【输出】 输出一行,包含一个整数,表示铺满整个客厅的方案数。由于这个数可能很大,只需输出它除以20110520的余数。 【样例输入1】 2 2 *_ __ 【样例输出1】 1 【样例输入2】 3 3 ___ _*_ ___ 【样例输出2】 8 【数据范围】 测试点编号 数据范围 1, 2 R*C <= 25 3, 4, 5 R*C<=100并且(R = 2或者C = 2) 6, 7, 8, 9, 10 R*C <= 100这是一道基于连通性的状态压缩动态规划。
插图如下:
状态转移的时候,要用一个开散列记录状态,避免多次枚举。
Accode:
#include <cstdio> #include <cstring> #include <cstdlib> #include <bitset> const char fi[] = "floor.in"; const char fo[] = "floor.out"; const int maxR = 110; const int maxN = 1 << 18; const int MOD = 20110520; const int HMOD = 262143; struct Node { int S, ID; Node *next; }; std::bitset <maxR> mp[maxR]; Node *Hash[HMOD + 1]; Node tmp[maxN]; int cnt[2]; int f[2][maxN]; int state[2][maxN]; //以上三个数组使用零壹滚动。 int n, m, pst, ths; void init_file() { freopen(fi, "r", stdin); freopen(fo, "w", stdout); return; } void readdata() { scanf("%d%d", &n, &m); for (int i = 1; i < n + 1; ++i) { getchar(); for (int j = 1; j < m + 1; ++j) if (getchar() == '_') { if (m > n) mp[j].set(i); else mp[i].set(j); } } if (m > n) std::swap(m, n); return; } inline void recover() { cnt[ths] = 0; memset(Hash, 0, sizeof(Hash)); return; } //每次需要清空,为下次的状态转移做好空间。 inline int get_ID(int S) { unsigned h = S & HMOD; for (Node *p = Hash[h]; p; p = p -> next) if (p -> S == S) return p -> ID; //若该状态已经被生成,则直接返回它的序号。 //否则新建一个。 tmp[++cnt[ths]].S = S; tmp[cnt[ths]].ID = cnt[ths]; tmp[cnt[ths]].next = Hash[h]; Hash[h] = &tmp[cnt[ths]]; f[ths][cnt[ths]] = 0; state[ths][cnt[ths]] = S; return cnt[ths]; } inline void Add(int &a, int b) { a += b; if (a >= MOD) a -= MOD; return; } void work() { pst = 0, ths = 1; //零壹滚动中的两指针。 recover(); f[ths][get_ID(0)] = 1; //置初始值为1。 for (int i = 1; i < n + 1; ++i) for (int j = 1; j < m + 1; ++j) { std::swap(pst, ths); recover(); //每次要记得清空。 int q = (m - j) << 1; //记录当前需要转移的右插头的位置(相对于最右端)。 int p = q + 2; //记录当前需要转移的下插头的位置(相对于最右端)。 for (int k = 1; k < cnt[pst] + 1; ++k) { int val = f[pst][k]; //当前状态的方案数。 int Last = state[pst][k]; //当前状态对应的四进制数。 if (j == 1) { if (Last & 3) continue; else Last >>= 2; } //若换行,首先判断末尾是否有多余的插头, //若有,则此状态作废;否则将末尾的插头 //移动到下一行的开头,以便下一行的转移。 int wp = (Last >> p) & 3; //当前的右插头。 int wq = (Last >> q) & 3; //当前的下插头。 int Now = Last - (wp << p) - (wq << q); //构造一个新的插头,将转移的目标状态 //所对应的两个插头先清空。 if (!mp[i][j]) { if (!wp && !wq) Add(f[ths][get_ID(Now)], val); //若该方块为障碍且无插头则可行。 } else { if (!wp && !wq) //无插头的情况,见图1-0。 //此时可以新建插头,分三种情况讨论。 { Add(f[ths][get_ID(Now | (1 << p))], val); //见图1-2。 Add(f[ths][get_ID(Now | (1 << q))], val); //见图1-1。 Add(f[ths][get_ID(Now | (3 << p) | (3 << q))], val); //见图1-3。 } else if (!wp || !wq) //只有一个插头的情况。见图2-0(1),2-0(2), //2-0(3)以及2-0(4),也要分情况讨论。 { Add(f[ths][get_ID(Now | (wp << q) | (wq << p))], val); //延续原来的插头,见图2-1(1)和2-1(2)。 if (wp == 1) //见图2-0(2)。 Add(f[ths][get_ID(Last | (2 << p))], val); //插头可在此转弯,见图2-2。 else if (wq == 1) Add(f[ths][get_ID(Last | (2 << q))], val); //同理如上。 else if (wp == 3 || wq == 3) //如图2-0(4)。 Add(f[ths][get_ID(Now)], val); //插头可在次终止,见图2-3。 } else if (wp == 1 && wq == 1) //两插头相遇,可以构成一个L,见图3-0。 Add(f[ths][get_ID(Now)], val); //见图3-1。 } } } printf("%d\n", f[ths][get_ID(0)]); return; } int main() { init_file(); readdata(); work(); return 0; }第二次做:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <string> const char fi[] = "floor.in"; const char fo[] = "floor.out"; const int maxN = 110; const int maxSTATUS = 0x100000; const int HMOD = 0x3ffff; const int MOD = 20110520; struct Node {int S, ID; Node *next;}; Node tmp[maxSTATUS], *Hash[HMOD + 1]; char mp[maxN][maxN]; int f[2][maxSTATUS], status[2][maxSTATUS]; int cnt[2], n, m, pst, ths = 1; void init_file() { freopen(fi, "r", stdin); freopen(fo, "w", stdout); return; } void readdata() { scanf("%d%d", &n, &m); for (int i = 0; i < n; ++i) { scanf("\n"); for (int j = 0; j < m; ++j) { char ch = getchar(); if (m > n) mp[j][i] = ch; else mp[i][j] = ch; } } if (m > n) std::swap(n, m); return; } inline int get_ID(int S) { int h = S & HMOD; for (Node *p = Hash[h]; p; p = p -> next) if (p -> S == S) return p -> ID; int ID = cnt[ths]; tmp[ID].ID = ID; tmp[ID].S = S; tmp[ID].next = Hash[h]; Hash[h] = &tmp[ID]; f[ths][ID] = 0; status[ths][ID] = S; return cnt[ths]++; } inline void Add(int &a, int b) {a += b; if (a >= MOD) a -= MOD; return;} void work() { f[ths][get_ID(0)] = 1; for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) { std::swap(pst, ths); cnt[ths] = 0; memset(Hash, 0, sizeof Hash); int p = (m - j) << 1, q = p - 2; for (int k = 0; k < cnt[pst]; ++k) { int val = f[pst][k]; int Last = status[pst][k]; if (!j) { if (Last & 3) continue; else Last >>= 2; } int wp = (Last >> p) & 3, wq = (Last >> q) & 3, Now = Last - (wp << p) - (wq << q); if (mp[i][j] == '*') {if (!wp && !wq) Add(f[ths][get_ID(Now)], val);} else if (!wp && !wq) { Add(f[ths][get_ID(Now | (1 << p))], val); Add(f[ths][get_ID(Now | (1 << q))], val); Add(f[ths][get_ID(Now | (3 << p) | (3 << q))], val); } else if (!wp || !wq) { Add(f[ths][get_ID(Now | (wp << q) | (wq << p))], val); if (wp == 1) Add(f[ths][get_ID(Last | (2 << p))], val); else if (wq == 1) Add(f[ths][get_ID(Last | (2 << q))], val); else Add(f[ths][get_ID(Now)], val); } else if (wp == 1 && wq == 1) Add(f[ths][get_ID(Now)], val); } } printf("%d\n", f[ths][get_ID(0)]); return; } int main() { init_file(); readdata(); work(); return 0; }