插头DP
题意:给出一个最多12*12的地形描述,问有多少方式在其上建一条F1赛道。一条F1赛道为仅横竖相连且覆盖所有空地的一个环。
参考了陈丹琦的《基于连通性状态压缩的动态规划问题》,插头+轮廓线扫描。虽然论文已经给出了基本思路,但仍存在一些特殊情况,如:
1 2 |
0 1 2 3 4 5 6 7 * ( ( ( ) ( ) ) ) |
应转变为
1 2 |
0 1 2 3 4 5 6 7 * * * ( ) ( ) ( ) |
我用了很长时间才发现这一点。
实现方面,一开始打算全部用位运算,但发现存储的时候状态数太多,无法直接开大数组存,于是又转成3进制。每次更新下一个格点的状态值时,交换内存指针,根据相应内存位置标记来判断是否置0,这样就避免了每次对大数组进行memset。另外,使用了栈来非重复地保存各状态(仅当计数值为0时入栈),避免了每次扫描整个计数数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define M 1594323 ll n,m,ei,ej,pn,ans; ll mem1[M],mem2[M],mem3[M],mem4[M]; ll *mp1,*mp2,*st1,*st2,sl1,sl2; char s[12][13],ms[M],msij; void init() { ll i,j; ei=ej=-1; ans=0; sl1=0; memset(ms,-1,sizeof ms); mp1=mem1; mp2=mem2; st1=mem3; st2=mem4; for(i=pn=0;i<n;i++) { scanf("%s",s[i]); for(j=0;j<m;j++) { if(s[i][j]=='.') { pn++; ei=i; ej=j; } } } } ll enc(ll u) { ll i,l; for(i=l=0;l<=m;l++) { i=i*3+(u&3); u>>=2; } return i; } ll dec(ll i) { ll u,l; for(u=l=0;l<=m;l++) { u=(u<<2)+i%3; i/=3; } return u; } ll chk(ll i,ll j) { if(i==n||j==m||s[i][j]!='.') return 1; return 0; } ll gt(ll u,ll j) { return (u>>(2*(m-j)))&3; } void st(ll &u,ll j,ll v) { u&=~(3LL<<(2*(m-j))); u|=v<<(2*(m-j)); } void ins(ll u,ll ud) { ll ml=enc(u); if(ms[ml]!=msij) { mp2[ml]=0; ms[ml]=msij; } if(!mp2[ml]) st2[sl2++]=ml; mp2[ml]+=ud; } void solve() { if(ei==-1||(pn&1)) { puts("0"); return; } ll i,j,k,l,ul,cnt; ll u,ud,v1,v2; ll ms=1,ml; for(l=0;l<=m;l++) ms*=3; mp1[enc(0)]=1; st1[sl1++]=enc(0); for(i=0;i<n;i++) { for(j=0;j<m;j++) { msij=i*m+j; if(s[i][j]=='*') continue; k=(chk(i+1,j)<<1)+(chk(i,j+1)); sl2=0; while(sl1) { ml=st1[--sl1]; u=dec(ml); ud=mp1[ml]; v1=gt(u,m); v2=gt(u,j); if(!v1&&!v2) { if(k==0) { st(u,j,1); st(u,m,2); ins(u,ud); } } else if(!v1||!v2) { if(k==0||k==1) { st(u,j,v1+v2); st(u,m,0); ins(u,ud); } if(k==0||k==2) { st(u,j,0); st(u,m,v1+v2); ins(u,ud); } } else if(v1!=v2) { if(v1==1&&v2==2&&(i!=ei||j!=ej)) continue; if(v1==1&&v2==2&&i==ei&&j==ej) ans+=ud; st(u,j,0); st(u,m,0); ins(u,ud); } else if(v1==1) { st(u,j,0); st(u,m,0); for(l=j+1,cnt=0;l<m;l++) { ul=gt(u,l); if(ul==1) cnt++; else if(ul==2) { if(!cnt) break; cnt--; } } st(u,l,1); ins(u,ud); } else { st(u,j,0); st(u,m,0); for(l=j-1,cnt=0;l>-1;l--) { ul=gt(u,l); if(ul==2) cnt++; else if(ul==1) { if(!cnt) break; cnt--; } } st(u,l,2); ins(u,ud); } } swap(mp1,mp2); swap(st1,st2); sl1=sl2; } } printf("%lld\n",ans); } int main() { while(~scanf("%lld%lld",&n,&m)) { init(); solve(); } return 0; } |
在ural上用VS 2010只能跑出0.6S左右的时间。瓶颈应该是慢在位向量和3进制间转换上面,但也没有想到太好的优化了……
ps:小小感慨一下,连这种问题都可以DP,实在是不可思议。