POJ 1739 Tony's Tour (插头DP,轮廓线DP)
题意:给一个n*m的矩阵,其中#是障碍格子,其他则是必走的格子,问从左下角的格子走到右下角的格子有多少种方式。
思路:
注意有可能答案是0,就是障碍格子阻挡住了去路。
插头DP有两种比较常见的表示连通信息的方式:
(1)最小表示法
(2)括号表示法
本文用括号表示法实现。左括号为1,右括号为2,用两个位来表示。轮廓线上最多需要表示9个插头信息,那么就是18个位即可。插头的状态转移有如下几种:
(1)右插头和下插头都是同个方向的括号,则合并他们,再将对应的两外两个半括号给改成一对。比如 ((())) 合并完变成##()(),橙色的就是需要改的地方。
(2)右插头是')',下插头是'(',则合并他们,且无需任何修改。
(3)右插头是'(',下插头是')',则不能合并,因为一旦合并,肯定是组成1个圆了,就会有多个连通分量的产生。自己画画就知道了。
(4)右插头是'(',下插头是')',只有在最后一个非障碍格子(按行从左到右遍历的)的时候才可以合并,
(5)右/下插头只有1个插头存在,那么可以延续它,可以分别往下和右两个方向。
(6)没有插头,那么只能另开一对新括号了,分别对应右和下插头的位置。
(7)障碍格子,只有该位置的两个插头都是空的时候才可以转移,且轮廓线无需修改。
因为状态本来就不多,用哈希表来存状态会比较快且比较省时间,哈希表实现是摘别人的。每次只需要用上一个格子中的状态来转移到当前格子的状态。本题是不能有连通分量产生的,所以只需要在初始的状态设置起点和终点是一对括号,那就相当于在找哈密顿回路了,和Formula 1就一样了。
1 //#include <bits/stdc++.h> 2 #include <iostream> 3 #include <map> 4 #include <vector> 5 #include <cstdio> 6 #include <cstring> 7 #define pii pair<int,int> 8 #define INF 0x3f3f3f3f 9 #define LL long long 10 using namespace std; 11 const int N=10; 12 char g[N][N]; 13 int cur, n, m, ex, ey; 14 struct Hash_Map 15 { 16 static const int mod=12357; 17 static const int N=50000; 18 int head[mod]; //桶指针 19 int next[N]; //链 20 int status[N]; //状态 21 LL value[N]; //状态对应的DP值。 22 int size; 23 void clear() 24 { 25 memset(head, -1, sizeof(head)); 26 size = 0; 27 } 28 29 void insert(int st, LL val) //插入状态st的值为val 30 { 31 int h = st%mod; 32 for(int i=head[h]; i!=-1; i=next[i]) 33 { 34 if(status[i] == st) //这个状态已经存在,累加进去。 35 { 36 value[i] += val; 37 return ; 38 } 39 } 40 status[size]= st; //新的 41 value[size] = val; 42 next[size] = head[h] ; //新插入的元素在队头 43 head[h] = size++; 44 } 45 }hashmap[2]; 46 47 48 int getbit(int s,int pos) //取出状态s的第pos个插头 49 { 50 int bit=0; 51 if(s&(1<<2*pos)) bit+=1; 52 if(s&(1<<2*pos+1)) bit+=2; 53 return bit; 54 } 55 int setbit(int s,int pos,int bit) //将状态s的第pos个插头设置为bit 56 { 57 if(s&(1<<2*pos )) s^=1<<2*pos; 58 if(s&(1<<2*pos+1)) s^=1<<2*pos+1; 59 return s|(bit<<2*pos); 60 } 61 62 int Fr(int s,int pos,int bit) //寻找状态s的第pos个插头对应的右括号。 63 { 64 int cnt=0; 65 for(pos+=2; pos<m; pos++) 66 { 67 if(getbit(s,pos)==3-bit) cnt++; 68 if(getbit(s,pos)==bit) cnt--; 69 if(cnt==-1) return setbit(s, pos, 3-bit); 70 } 71 72 } 73 int Fl(int s,int pos,int bit) //寻找状态s的第pos个插头对应的左括号。 74 { 75 int cnt=0; 76 for(pos--; pos>=0; pos--) 77 { 78 if(getbit(s,pos)==3-bit) cnt++; 79 if(getbit(s,pos)==bit) cnt--; 80 if( cnt==-1) return setbit(s, pos, 3-bit); 81 } 82 83 } 84 85 void DP(int i,int j) //状态转移 86 { 87 for(int k=0; k<hashmap[cur^1].size; k++) 88 { 89 int s=hashmap[cur^1].status[k]; 90 int v=hashmap[cur^1].value[k]; 91 int R=getbit(s,j), D=getbit(s,j+1); 92 if(g[i][j]=='.') 93 { 94 if(R && D) //两个括号 95 { 96 int t=setbit(s,j,0)&setbit(s,j+1,0); 97 if(R==D) //同个方向的括号 98 { 99 if(R==1) t=Fr(t,j,2); //要改 100 else t=Fl(t,j,1); 101 hashmap[cur].insert(t,v); 102 } 103 else if( R==2 && D==1 ) //不同方向括号 104 hashmap[cur].insert(t,v); 105 else if(i==ex&&j==ey) 106 hashmap[cur].insert(t,v); 107 } 108 else if(R || D) //仅1个括号 109 { 110 hashmap[cur].insert(s,v); 111 int t; 112 if(R) t=setbit(setbit(s,j,0),j+1,R); 113 else t=setbit(setbit(s,j+1,0),j,D); 114 hashmap[cur].insert(t,v); 115 } 116 else //无括号 117 hashmap[cur].insert( setbit(s,j,1)|setbit(s,j+1,2), v); 118 } 119 else if(R==0&&D==0) //障碍格子 120 hashmap[cur].insert(s, v); 121 } 122 } 123 void cal() 124 { 125 for(int i=0; i<n; i++) 126 { 127 cur^=1; 128 hashmap[cur].clear(); 129 for(int j=0; j<hashmap[cur^1].size; j++) //新行,需要左移一下状态。 130 if( getbit( hashmap[cur^1].status[j], m)==0 ) 131 hashmap[cur].insert( hashmap[cur^1].status[j]<<2, hashmap[cur^1].value[j] ); 132 for(int j=0; j<m; j++) 133 { 134 cur^=1; 135 hashmap[cur].clear(); 136 DP(i,j); 137 if(i==ex && j==ey) return ; //终点 138 } 139 } 140 } 141 142 bool print() 143 { 144 for(int i=0; i<hashmap[cur].size; i++) //寻找轮廓线状态为0的值。 145 { 146 int s=hashmap[cur].status[i]; 147 if(s==0) 148 { 149 printf("%lld\n", hashmap[cur].value[i]); 150 return true; 151 } 152 } 153 return false; 154 } 155 int main() 156 { 157 freopen("input.txt", "r", stdin); 158 while(scanf("%d%d",&n,&m), n+m) 159 { 160 ex=ey=cur=0; 161 for(int i=n-1; i>=0; i--) scanf("%s",g[i]); //反向存 162 for(int i=0; i<n; i++) //寻找终点格子:ex和ey 163 for(int j=0; j<m; j++) 164 if( g[i][j]=='.' ) 165 ex=i,ey=j; 166 167 hashmap[cur].clear(); 168 hashmap[cur].insert(setbit(0,0,1)|setbit(0,m-1,2), 1); //初始状态 169 cal(); 170 if(!print()) puts("0"); //无路可达 171 } 172 return 0; 173 }
附上哈希表实现:
1 struct Hash_Map 2 { 3 static const int mod=12357; 4 static const int N=50000; 5 int head[mod]; //桶指针 6 int next[N]; //记录链的信息 7 int status[N]; //状态 8 LL value[N]; //状态对应的DP值。 9 int size; 10 void clear() //清除哈希表中的状态 11 { 12 memset(head, -1, sizeof(head)); 13 size = 0; 14 } 15 16 void insert(int st, LL val) //插入状态st的值为val 17 { 18 int h = st%mod; 19 for(int i=head[h]; i!=-1; i=next[i]) 20 { 21 if(status[i] == st) //这个状态已经存在,累加进去。 22 { 23 value[i] += val; 24 return ; 25 } 26 } 27 status[size]= st; //找不到状态st,则插入st。 28 value[size] = val; 29 next[size] = head[h] ; //新插入的元素在队头 30 head[h] = size++; 31 } 32 }hashmap[2];