suffix array总算搞定了,集训还剩两周时间,搞一下插头DP
先挖个坑,这里将持续update一些插头的题解。
HDU 1693 Eat the Trees
简单多回路问题,多回路的优点就是简单,不需要维护连通性,之需要维护轮廓线上的插头即可。
这题最为新手入门题,包括了插头需要注意的几点事项。
第一点:按格转移比较快。
第二点:在按格转移过程之中,一定要注意当前格的左插头和上插头,由于是多回路,所以比较简单,在单回路问题上,需要对插头分类讨论,如果有独立插头讨论更加复杂。
第三点:如果有障碍物,讨论是需要注意,尤其是后面将要介绍的最小表示法中,对障碍物(或者是可不选取的格子)需要精确的讨论转移。
第四点:行间转移,在一行行末向下一行行初转移的过程中,需要注意行末位置的轮廓线上的最后一个位置(是一个左插头位置)不能有有插头。而行初位置的插头也不能有。所以转移时,要把行末的有效状态乘以一个进制数(X进制左移一位)。
代码如下:
然后是URAL 1519以及POJ 1739,后者也是男人8题之一。
这两道题是最简单的括号序列表示法,我们采用三进制状态压缩。
URAL求回路个数。依然是记录轮廓线上的状态。我们用0表示没有括号,1表示左括号,2表示右括号。并用左位表示该格子左边位置插头状态,上位表示该格子上面位置的插头状态。
1.首先预处理出所以合法状态,合法状态是插头不能交叉,而且每个右插头有匹配的左插头。
2.依然是按格转移,由于要维护括号匹配,插头合并,有的时候需要重新整理括号,转移的时候分情况讨论。
情况1.该节点不可通过,这时只有当前块的左位和上位头全部是0才可以转移。
情况2.该节点可以通过,且左位和上位全部是0。该情况在,可以新建立1对匹配的括号序列.
情况3.该节点可以通过,且左位和上位全部是1。这时要合并2个左括号,而且重新整理插头状态,找到右边第一个独立的右括号,把他变成左括号。
情况4.该节点可以通过,且左位和上位全部是2。这时要合并2个右括号,而且重新整理插头状态,找到左边第一个独立的左括号,把他变成右括号。
情况5.该节点可以通过,且左位和上位分别是右括号和左括号,这是合并2个括号,则不需要重新整理插头。
情况6.该节点可以通过,且左位和上位一个是0,一个不是0,这是又2种扩展,为一下格增加的左插头,或者为下一行增加一个上插头。
情况7.只有在最后一个合法的格子发生,就是合并一个左括号和一个右括号。
清楚的分析了上述7种情况以后,就可以直白的DP转移了(虽然有些不好写……不过在凶残的今天这个已经是入门级别的插头了……)。
本题还可以用hash优化,但是并没有使用,导致每次memset的状态过多,浪费了一定实践,将在后面的最小表示法中,描述hash优化。
代码如下:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 #define REP(i, l, n) for(int i = l;i < n;i++) 6 #define CC(con, i) memset(con, i, sizeof(con)) 7 typedef long long LL; 8 const int N = 14; 9 const int TOT = 50000; 10 const int MAXN = 1594323;// 3^13 11 char maps[N][N]; 12 int bit3[N] = {1}, status[TOT]; 13 int Hash[MAXN], allS = 0; 14 LL dp[2][TOT]; 15 bool check(int s) 16 { 17 int cnt = 0; 18 while(s) 19 { 20 int n = s % 3; 21 if(n == 1) cnt++; 22 if(n == 2) cnt--; 23 if(cnt < 0) return false; 24 s /= 3; 25 } 26 return (cnt == 0); 27 } 28 void preprocess() 29 { 30 REP(i, 1, N) bit3[i] = bit3[i - 1] * 3; 31 REP(i, 0, bit3[N - 1]) 32 { 33 if(check(i)) 34 { 35 Hash[i] = allS; 36 status[allS++] = i; 37 } 38 else Hash[i] = -1; 39 } 40 status[allS] = MAXN; 41 } 42 int getbit(int s, int i) 43 { 44 while(i-- > 0) s /= 3; 45 return s % 3; 46 } 47 void transfer(LL& dest, LL add) 48 { 49 dest == -1 ? (dest = add) : (dest += add); 50 } 51 LL DP(int n, int m, int px, int py) 52 { 53 LL ans = 0; 54 int now = 0, pre; 55 CC(dp, -1); 56 dp[0][0] = 1; 57 for(int i = 0; i < n; i++) 58 { 59 for(int j = 0; j < m; j++) 60 { 61 pre = now;now ^= 1; 62 CC(dp[now], -1); 63 for(int k = 0, s; s = status[k], s < bit3[m + 1]; k++) 64 { 65 if(dp[pre][k] == -1) continue; 66 int l = getbit(s, j), u = getbit(s, j + 1); 67 int nows = s - l * bit3[j] - u * bit3[j + 1]; 68 if(maps[i][j] == '*') 69 { 70 if(l == 0 && u == 0) 71 transfer(dp[now][k], dp[pre][k]); 72 } 73 else if(l == 0 && u == 0)//both down and right build 2 plugin 74 { 75 if(maps[i][j + 1] == '.' && maps[i + 1][j] == '.') 76 { 77 int nxt = nows + bit3[j] + 2 * bit3[j + 1]; 78 transfer(dp[now][Hash[nxt]], dp[pre][k]); 79 } 80 } 81 else if(l == 1 && u == 1)// merge (( make )) to () 82 { 83 int cnt = 0; 84 for(int b = j + 2; b <= m; b++) 85 { 86 int tmp = getbit(nows, b); 87 if(tmp == 2) cnt--; 88 if(tmp == 1) cnt++; 89 if(cnt == -1) 90 { 91 transfer(dp[now][Hash[nows - bit3[b]]], dp[pre][k]); 92 break; 93 } 94 } 95 } 96 else if(l == 2 && u == 2)// merge )) make (( to () 97 { 98 int cnt = 0; 99 for(int b = j - 1;b >= 0;b--) 100 { 101 int tmp = getbit(nows, b); 102 if(tmp == 1) cnt++; 103 if(tmp == 2) cnt--; 104 if(cnt == 1) 105 { 106 transfer(dp[now][Hash[nows + bit3[b]]], dp[pre][k]); 107 break; 108 } 109 } 110 } 111 else if(l == 1 && u == 2)//merge () at last grid 112 { 113 if(px == i && py == j) 114 ans += dp[pre][k]; 115 } 116 else if(l == 2 && u == 1)//merge )( 117 { 118 transfer(dp[now][Hash[nows]], dp[pre][k]); 119 } 120 else if((!l && u) || (l && !u)) 121 { 122 if(maps[i + 1][j] == '.') 123 transfer(dp[now][Hash[nows + (l + u) * bit3[j]]], dp[pre][k]); 124 if(maps[i][j + 1] == '.') 125 transfer(dp[now][Hash[nows + (l + u) * bit3[j + 1]]], dp[pre][k]); 126 } 127 } 128 } 129 pre = now;now ^= 1; 130 CC(dp[now], -1);//must CC -1 131 for(int k = 0, s; s = status[k], s < bit3[m]; k++) 132 if(dp[pre][k] != -1) 133 dp[now][Hash[s * 3]] = dp[pre][k]; 134 } 135 return ans; 136 } 137 int main() 138 { 139 int n, m, px, py; 140 preprocess(); 141 while(scanf("%d %d", &n, &m) == 2) 142 { 143 CC(maps, 0); 144 REP(i, 0, n) scanf("%s", maps[i]); 145 REP(i, 0, n) REP(j, 0, m) if(maps[i][j] == '.') px = i, py = j; 146 printf("%lld\n", DP(n, m, px, py)); 147 } 148 return 0; 149 }
而对于POJ 1739。
我们可以通过构造方法,再外围加上1圈墙,然后改造成 ural 1519的方法解决问题。但是速度较慢,因为扩展的原因,将导致状态增多。复杂度遍高。实际上本题又更好的方法,因为题目要求从最后一行的第一个格子走到最后一行的最后一个格子。所以我们最后只需要统计最终状态为01000002的个数既可。同时还避免了情况7个讨论。
而且这样处理后状态极少,每格的合法状态只有不到1000个,复杂度大大降低了,也不许要用hash之类的优化了。
代码如下:
1 #include <cstring> 2 #include <cstdio> 3 #include <algorithm> 4 #include <string> 5 #include <iostream> 6 using namespace std; 7 #define CC(i, v) memset(i, v, sizeof(i)) 8 #define REP(i, l, n) for(int i = l;i < int(n);++i) 9 typedef long long LL; 10 const int N = 10; 11 const int TOT = 1000; 12 const int MAXN = 1594323;// 3^13 13 char maps[N][N]; 14 int bit3[N] = {1}, status[TOT]; 15 int Hash[MAXN], allS = 0; 16 LL dp[2][TOT]; 17 bool check(int s) { 18 int cnt = 0; 19 while(s) { 20 int n = s % 3; 21 if(n == 1) cnt++; 22 if(n == 2) cnt--; 23 if(cnt < 0) return false; 24 s /= 3; 25 } 26 return (cnt == 0); 27 } 28 void preprocess() { 29 REP(i, 1, N) bit3[i] = bit3[i - 1] * 3; 30 REP(i, 0, bit3[N - 1]) { 31 if(check(i)) { 32 Hash[i] = allS; 33 status[allS++] = i; 34 } else { 35 Hash[i] = -1; 36 } 37 } 38 status[allS] = MAXN; 39 } 40 int getbit(int s, int i) { 41 return s / bit3[i] % 3; 42 } 43 void transfer(LL& dest, LL add) { 44 dest == -1 ? (dest = add) : (dest += add); 45 } 46 LL DP(int n, int m) { 47 int now = 0, pre = 1; 48 CC(dp, -1); 49 dp[0][0] = 1; 50 for(int i = 0; i < n; i++) { 51 for(int j = 0; j < m; j++) { 52 swap(now, pre); 53 CC(dp[now], -1); 54 for(int k = 0, s; s = status[k], s < bit3[m + 1]; k++) { 55 if(dp[pre][k] == -1) continue; 56 int l = getbit(s, j), u = getbit(s, j + 1); 57 int nows = s - l * bit3[j] - u * bit3[j + 1]; 58 if(maps[i][j] != '.') { 59 if(l == 0 && u == 0) { 60 transfer(dp[now][k], dp[pre][k]); 61 } 62 } else if(l == 0 && u == 0) { //当向下向右都可以走的时候建立两个插头 63 if(maps[i][j + 1] == '.' && maps[i + 1][j] == '.') { 64 int nxt = nows + bit3[j] + 2 * bit3[j + 1]; 65 transfer(dp[now][Hash[nxt]], dp[pre][k]); 66 } 67 } else if(l == 1 && u == 1) { //合并2个左插头,整理相应的右插头 68 int cnt = 0; 69 for(int b = j + 2; b <= m; b++) { 70 int tmp = getbit(nows, b); 71 if(tmp == 2) cnt--; 72 if(tmp == 1) cnt++; 73 if(cnt == -1) { 74 transfer(dp[now][Hash[nows - bit3[b]]], dp[pre][k]); 75 break; 76 } 77 } 78 } else if(l == 2 && u == 2) { //合并2个右插头,整理相应的左插头 79 int cnt = 0; 80 for(int b = j - 1;b >= 0;b--) { 81 int tmp = getbit(nows, b); 82 if(tmp == 1) cnt++; 83 if(tmp == 2) cnt--; 84 if(cnt == 1) { 85 transfer(dp[now][Hash[nows + bit3[b]]], dp[pre][k]); 86 break; 87 } 88 } 89 } else if(l == 2 && u == 1) { //合并)(,什么都不用整理 90 transfer(dp[now][Hash[nows]], dp[pre][k]); 91 } else if((!l && u) || (l && !u)) { 92 if(maps[i + 1][j] == '.') 93 transfer(dp[now][Hash[nows + (l + u) * bit3[j]]], dp[pre][k]); 94 if(maps[i][j + 1] == '.') 95 transfer(dp[now][Hash[nows + (l + u) * bit3[j + 1]]], dp[pre][k]); 96 } 97 } 98 } 99 swap(pre, now); 100 CC(dp[now], -1);//必须CC一下,把不合法的状态设为-1 101 for(int k = 0, s; s = status[k], s < bit3[m]; k++) 102 if(dp[pre][k] != -1) 103 dp[now][Hash[s * 3]] = dp[pre][k]; 104 } 105 int state = (1 + 2 * bit3[m - 1]) * 3; 106 return dp[now][Hash[state]]; 107 } 108 int main() { 109 int n, m; 110 preprocess(); 111 while(scanf("%d %d", &n, &m) == 2 && (n + m)) { 112 CC(maps, 0); 113 REP(i, 0, n) scanf("%s", maps[i]); 114 maps[n][0] = maps[n][m - 1] = '.'; 115 LL res = DP(n, m); 116 printf("%I64d\n", res == -1 ? 0 : res); 117 } 118 return 0; 119 }
hdu 3377也是这个类型的题目,
题目要求从左上角走到右下角的路径权值最大,我们在外面构造一圈墙(构墙方法自己YY),然后用连通性DP求一个最大值即可。
需要注意的是
1.过程中取最大值,而不是累加。
2.除了左上角第一格,其余情况当遇到左位上位均是0的情况,可以不选择该点。
3.合并左位和上位分别是右括号和左括号情况依然之发生在最后一个合法位置。
4.墙权值要足够小,我给的是-INF(4e8)。防止最后不是回路,或者非法回路。
然后就依然是这类DP的经典转移方法。不符代码了
然后我们加深一下难度,用矩阵乘法加速DP的转移过程,zoj 3256就是这样提到题,首先预处理状态直接的转移,然后用矩阵加速。
首先用枚举一层的开始状态(设为S),然后用插头的转移方式,逐个递推,知道最后一格推完位置,设太状态为T,则S->T就是一个层间转移的合法状态。
穷举之后,就可以用矩阵转移了。
注意事项:
1.矩阵有127行列,递归容易暴栈,最好迭代快速幂。
2.由于特别起点和终点都在第一列,所以我们不需要加墙,只需要特别处理计数时的合法状态。
3.模数很小,可以计算完每个格子的时候再模,不需要计算一步模一步,不然会超时的。
注意以上事项之后就一道简单插头+矩阵运算的题目了……(还是很难写的……正式比赛遇到插头需要慎重,即使只是简单插头……)
代码如下:
1 #include <iostream> 2 #include <cstring> 3 #include <map> 4 #include <cstdio> 5 #include <vector> 6 #include <algorithm> 7 #include <cassert> 8 #define REP(i, l, n) for(int i = l;i < int(n);i++) 9 using namespace std; 10 const int N = 9; 11 const int MOD = 7777777; 12 typedef long long LL; 13 int bit3[N] = {1}; 14 vector<int> state; 15 map<int, int> id_table; 16 const int maxn = 127; 17 struct Matrix { 18 LL A[maxn][maxn]; 19 int size; 20 Matrix() { 21 memset(this, 0, sizeof (*this)); 22 } 23 void set_E() { 24 for(int i = 0;i < size;i++) { 25 A[i][i] = 1; 26 } 27 } 28 }; 29 Matrix operator*(const Matrix& m1, const Matrix& m2) { 30 Matrix ret; 31 ret.size = m1.size; 32 for (int i = 0; i < ret.size; ++i) 33 for (int j = 0; j < ret.size; ++j) { 34 ret.A[i][j] = 0; 35 for (int k = 0; k < ret.size; ++k) { 36 ret.A[i][j] += m1.A[i][k] * m2.A[k][j]; 37 } 38 ret.A[i][j] %= MOD; 39 } 40 return ret; 41 } 42 Matrix mypower(const Matrix& m, int n) { 43 Matrix ret, tmp = m; 44 ret.size = m.size; 45 ret.set_E(); 46 while(n != 0) { 47 if(n & 1) { 48 ret = ret * tmp; 49 } 50 n >>= 1; 51 tmp = tmp * tmp; 52 } 53 return ret; 54 } 55 Matrix A; 56 bool check(int s) { 57 int cnt = 0, n; 58 while(s) { 59 n = s % 3; 60 if(n == 1) cnt++; 61 if(n == 2) cnt--; 62 if(cnt < 0) return false; 63 s /= 3; 64 } 65 return (cnt == 0); 66 } 67 int getbit(int s, int i) { 68 while(i-- > 0) s /= 3; 69 return s % 3; 70 } 71 int n, m; 72 void dfs(const int pre_state, int cur_state, const int step, const int n) { 73 if(step == n) { 74 if(cur_state < bit3[n]) { 75 cur_state *= 3; 76 int a = id_table[pre_state]; 77 map<int, int>::iterator iter = id_table.find(cur_state); 78 if(iter != id_table.end()) { 79 A.A[a][iter->second]++; 80 } 81 } 82 return; 83 } 84 int l = getbit(cur_state, step), u = getbit(cur_state, step + 1); 85 int nows = cur_state - l * bit3[step] - u * bit3[step + 1]; 86 if(l == 0 && u == 0) { //当向下向右都可以走的时候建立两个插头 87 int nxt = nows + bit3[step] + 2 * bit3[step + 1]; 88 dfs(pre_state, nxt, step + 1, n); 89 } else if(l == 1 && u == 1) { // 合并2个左插头,整理相应的右插头 90 int cnt = 0; 91 for(int b = step + 2; b <= m; b++) { 92 int tmp = getbit(nows, b); 93 if(tmp == 2) cnt--; 94 if(tmp == 1) cnt++; 95 if(cnt == -1) { 96 dfs(pre_state, nows - bit3[b], step + 1, n); 97 break; 98 } 99 } 100 } else if(l == 2 && u == 2) { // 合并2个右插头,整理相应的左插头 101 int cnt = 0; 102 for(int b = step - 1;b >= 0;b--) { 103 int tmp = getbit(nows, b); 104 if(tmp == 1) cnt++; 105 if(tmp == 2) cnt--; 106 if(cnt == 1) { 107 dfs(pre_state, nows + bit3[b], step + 1, n); 108 break; 109 } 110 } 111 } else if(l == 2 && u == 1) { //合并)(,什么都不用整理 112 dfs(pre_state, nows, step + 1, n); 113 } else if((!l && u) || (l && !u)) { 114 dfs(pre_state, nows + (l + u) * bit3[step], step + 1, n); 115 dfs(pre_state, nows + (l + u) * bit3[step + 1], step + 1, n); 116 } 117 } 118 int main() { 119 REP(i, 1, N) bit3[i] = bit3[i - 1] * 3; 120 REP(i, 0, bit3[N - 1]) { 121 if(i % 3 == 0 && check(i)) { 122 id_table[i] = state.size(); 123 state.push_back(i); 124 } 125 } 126 while(cin >> n >> m) { 127 A = Matrix(); 128 int _size = 0; 129 for(int i = 0;i < int(state.size()) && state[i] < bit3[n + 1];i++) { 130 dfs(state[i], state[i], 0, n); 131 _size++; 132 } 133 A.size = _size; 134 Matrix B = mypower(A, m); 135 int b = id_table[(1 + 2 * bit3[n - 1]) * 3]; 136 LL res = B.A[0][b]; 137 if(res == 0) { 138 cout << "Impossible" << endl; 139 } else { 140 cout << res << endl; 141 } 142 } 143 return 0; 144 }