ZOJ 3256 Tour in the Castle 插头DP 矩阵乘法
题解
这题是一道非常好的插头题,与一般的按格转移的题目不同,由于m很大,要矩阵乘法,这题需要你做一个按列转移的插头DP。
按列转移多少与按格转移不同,但大体上还是基于连通性进行转移。每一列只有右插头是对下一列的转移有影响的,那么我们只需要记录每一列的右插头的连通情况,用最小表示法表示为当前列的状态。在转移的时候,我们只知道上一列的右插头,即本列的左插头的情况,而上插头还需要自己记一个标记。
那么我们具体分析:
1、不存在上插头
1、同时存在左插头和右插头,不需要修改当前插头,直接把上一列的右插头当做当前列的右插头
2、只在左插头,即从上一列的某一个连通块转移过来,记录连通块。(左下插头)
3、只在右插头,即此为一个新的连通块,打上标记,表明这是一个新的连通块。(右下插头)
2、存在上插头
1、同时存在左插头和右插头,一个格子里有三个插头,非法状态
2、都不存在左插头和右插头,不需要修改当前插头,即从上往下。
3、存在左插头
1、上插头和左插头同属一个连通块,但不在最终状态(没有右插头)的右下角的格子里出现,非法状态
2、上插头是左下插头,合并连通块,并删除这两个插头(这个合并比较特殊,因为两个都是已知的连通块,具体画图比较清晰)
3、上插头是右下插头,合并连通块,删掉当前插头
4、不存在左插头
1、上插头是左下插头,合并连通块,删除左下插头
2、上插头是右下插头,合并为新的连通块
具体情况还是自己动手画图比较清晰。
然后就到了矩乘的部分。首先考虑构造矩阵,g[i][j] = 1表示i状态能推到j状态,因此我们只需要枚举这些状态,一个一个判转移就可以了。
初始的情况下,只存在全空的状态和0和N-1有插头的情况,因此答案就是矩阵快速幂后的ans[1][0],即从初始状态推向终止状态。
程序
1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <string> 5 #include <algorithm> 6 7 using namespace std; 8 9 #define REP(i, a, b) for (int i = (a), i##_end_ = (b); i <= i##_end_; ++i) 10 #define DWN(i, a, b) for (int i = (a), i##_end_ = (b); i >= i##_end_; --i) 11 #define mset(a, b) memset(a, b, sizeof(a)) 12 typedef long long LL; 13 const int MAXD = 15, HASH = 419, STATE = 1010, MOD = 7777777; 14 int n, m, code[MAXD], ch[MAXD]; 15 struct HASHMAP 16 { 17 int head[HASH], nxt[STATE], state[STATE], siz; 18 void clear() { siz = 0, mset(head, -1); } 19 int push(int x) 20 { 21 int pos = x%HASH, i = head[pos]; 22 for (; i != -1; i = nxt[i]) 23 if (state[i] == x) return i; 24 state[siz] = x; 25 nxt[siz] = head[pos], head[pos] = siz++; 26 return siz-1; 27 } 28 }hm; 29 struct Matrix 30 { 31 int mat[200][200], D; 32 Matrix operator * (const Matrix &AI) const 33 { 34 Matrix ret; ret.D = D; 35 REP(i, 0, D) 36 REP(j, 0, D) 37 { 38 LL sum = 0; 39 REP(k, 0, D) sum += (LL)mat[i][k]*AI.mat[k][j]; 40 ret.mat[i][j] = sum%MOD; 41 } 42 return ret; 43 } 44 }rc[30], A, B; 45 46 void decode(int x) 47 { 48 DWN(i, n, 1) code[i] = x&3, x >>= 2; 49 } 50 51 int encode() 52 { 53 int cnt = 0, ret = 0; 54 mset(ch, -1), ch[0] = 0; 55 REP(i, 1, n) 56 { 57 if (ch[code[i]] == -1) ch[code[i]] = ++cnt; 58 ret <<= 2, ret |= ch[code[i]]; 59 } 60 return ret; 61 } 62 63 bool check(int st, int nxt) 64 { 65 decode(st); 66 int up = 0, k, cnt = 0; 67 REP(i, 1, n) 68 { 69 if (up == 0) 70 { 71 if (!code[i] && !(nxt&(1<<(i-1)))) return false; 72 if (code[i] && (nxt&(1<<(i-1)))) continue ; 73 if (code[i]) up = code[i]; 74 else up = -1; 75 k = i; 76 } 77 else 78 { 79 if (code[i] && (nxt&(1<<(i-1)))) return false; 80 if (!code[i] && !(nxt&(1<<(i-1)))) continue ; 81 if (code[i]) 82 { 83 if (up == code[i] && !(nxt&(1<<(i-1))) && (nxt || i != n)) return false; 84 if (up != -1) 85 { 86 REP(j, 1, n) if (code[j] == code[i] && j != i) code[j] = code[k]; 87 code[i] = code[k] = 0; 88 } 89 else code[k] = code[i], code[i] = 0; 90 } 91 else 92 { 93 if (up != -1) code[i] = code[k], code[k] = 0; 94 else code[i] = code[k] = n+(++cnt); 95 } 96 up = 0; 97 } 98 } 99 if (up) return false; 100 return true; 101 } 102 103 void init() 104 { 105 if (rc[n].D != 0) 106 { B = rc[n]; return ; } 107 mset(rc[n].mat, 0); 108 hm.clear(), hm.push(0); 109 mset(code, 0), code[1] = code[n] = 1, hm.push(encode()); 110 decode(hm.state[1]); 111 for (int i = 1; i < hm.siz; ++i) 112 REP(nxt, 0, ((1<<n)-1)) 113 if (check(hm.state[i], nxt)) 114 { 115 int j = hm.push(encode()); 116 rc[n].mat[i][j] ++; 117 } 118 rc[n].D = hm.siz-1; 119 B = rc[n]; 120 } 121 122 void work() 123 { 124 mset(A.mat, 0); A.D = B.D; 125 REP(i, 0, A.D) A.mat[i][i] = 1; 126 while (m > 0) 127 { 128 if (m&1) A = A*B; 129 B = B*B, m >>= 1; 130 } 131 if (!A.mat[1][0]) puts("Impossible"); 132 else printf("%d\n", A.mat[1][0]); 133 } 134 135 int main() 136 { 137 REP(i, 0, 20) rc[i].D = 0; 138 while (~scanf("%d %d", &n, &m)) 139 init(), work(); 140 return 0; 141 }