插头dp
众所周知,shzr不擅长dp,所以她决定多学几个算法来提高(看上去的)知识水平。
插头dp:基于连通性的状态压缩动态规划;
接下来以这道例题:[模板]插头dp 来学习插头dp;
[模板]插头dp
题意概述
状态设计
插头是什么呢?插头就是表示一个格子上是否有未结束的路径的一种方法(有的话就可以继续延伸);
每个格子可能存在六种插头:上下,左右,上左,上右,下左,下右;6种感觉太复杂了,我们对其进一步转化,每次只考虑一条分割线上的插头,那么就只需要考虑每个位置是否有插头了;
因为状态比较复杂,考虑一格一格地转移,具体做法是状压轮廓线;
注意:状压的是轮廓线(图左)而不是轮廓上的格子(图右);
所以每个状态应该包含m+1位而不是m位;
因为需要最后连成一个回路,所以需要记录插头的联通性。可以发现,如果轮廓线上依次存在abcd四个插头,那不可能出现ac联通,bd联通,而ab之间却不联通的情况,这与括号匹配有一点相似;所以我们对于每个插头分配一个括号来表示轮廓线上的联通性。当然...有些线上可能并没有插头,所以还需要再一种状态表示。那么,每段轮廓线就有三种状态:左括号,右括号,无括号;因为位运算比较快,我们用四进制状压表示这个状态;这样有一个小小的坏处,就是很多状态根本不存在,所以可以考虑将所有存在的状态进行Hash,每次只用这些状态进行转移;
现在,我们就得到了这道题的dp状态设计:dp[i][j][k]表示当前dp完了前i-1行以及第i行的前j个格子,轮廓线状态为k的方案数;
转移
转移情况有点多,下面会一一列举:
1.当前位置有障碍:如果这个格子的左边上边都没有插头,那么轮廓线状态不变,否则不能转移;(以下情况均默认当前格子无障碍)
2.左边上边都没有插头:可以考虑新建一个连通分量,也就是在当前格子加入右插头,下插头;同时,由于他们是联通的,所以直接加入一对括号;
3.左边有插头,上边没有;可以考虑向右延续插头,也可以拐弯向下;
4.上边有插头,左边没有;可以考虑向下延续插头,也可以拐弯向右;
上边左边都有插头的话,又分为多种情况:
5.都是左括号:这个比较有趣,可以发现,我们将j-1,j两个插头连接后,原本与j配对的右插头,由于和j-1这个左插头连着...变成左插头了!所以可以通过一个O(M)的循环找到这个右括号并进行更改;
6.都是右括号:和上面那种差别不大吧;
7.“)(”:在这里连一下,恰好联通了两个连通分量!不过呢,别忘了连接后这里就没插头了,要把这对括号一起删掉;
8.“()”:已经连通了!也就是说如果这里连一下的话,就形成回路了。所以要注意,如果当前格子不是最后一个空格的话,这个状态就不能转移,否则直接加进最终答案里;
实现技巧
假设我们当前考虑的格子是(i,j),那么在轮廓线上,可以通过:
来得到当前格子左边和上边的轮廓线情况;
一个要特殊考虑的情况是:换行时,需要把之前的所有状态左移两位,也就是舍弃最后一段轮廓线,在前面加一段无插头的轮廓线。这是为什么呢?这里有一张图来解释:(换行后,考虑的轮廓线从左边这样变成了右边这样)
代码

1 # include <cstdio> 2 # include <iostream> 3 # include <algorithm> 4 # include <queue> 5 # include <map> 6 # include <vector> 7 # include <cmath> 8 # include <cstring> 9 # define R register int 10 # define ll long long 11 12 using namespace std; 13 14 const int N=13; 15 const int Q=540005; 16 const int base=299989; 17 int n,m,a[N][N],no=0,las=1,tx,ty; 18 int bit[N],cnt[2],nex[Q],h[Q],t[2][Q]; 19 char s[N]; 20 ll ans,v[2][Q]; 21 //t[no][x]:x代表的真实状态; 22 //v[no][x]:x代表的真实状态的dp值; 23 24 void Hash (int id,ll val) 25 { 26 int x=id%base; 27 for (R i=h[x];i;i=nex[i]) 28 if(t[no][i]==id) { v[no][i]+=val; return; } 29 cnt[no]++; 30 nex[ cnt[no] ]=h[x]; h[x]=cnt[no]; 31 t[no][ cnt[no] ]=id; v[no][ cnt[no] ]=val; 32 } 33 34 int main() 35 { 36 scanf("%d%d",&n,&m); 37 for (R i=1;i<=n;++i) 38 { 39 scanf("%s",s+1); 40 for (R j=1;j<=m;++j) 41 { 42 if(s[j]=='*') continue; 43 a[i][j]=1,tx=i,ty=j; 44 } 45 } 46 bit[0]=1; for (R i=1;i<=m;++i) bit[i]=bit[i-1]<<2; 47 cnt[no]=1; t[no][1]=0; v[no][1]=1; 48 for (R i=1;i<=n;++i) 49 { 50 for (R k=1;k<=cnt[no];++k) t[no][k]<<=2; 51 for (R j=1;j<=m;++j) 52 { 53 swap(no,las); 54 memset(h,0,sizeof(h)); cnt[no]=0; 55 for (R k=1;k<=cnt[las];++k) 56 { 57 int id=t[las][k]; ll val=v[las][k]; 58 int lef=(id>>(2*(j-1)))%4,up=(id>>(2*j))%4; 59 if(!a[i][j]) 60 { 61 if(!lef&&!up) Hash(id,val); 62 } 63 else if(!lef&&!up) 64 { 65 if(a[i+1][j]&&a[i][j+1]) 66 Hash(id+bit[j-1]+2*bit[j],val); //新建插头 67 } 68 else if(!lef&&up) 69 { 70 if(a[i+1][j]) Hash(id-bit[j]*up+bit[j-1]*up,val); 71 if(a[i][j+1]) Hash(id,val); 72 } 73 else if(lef&&!up) 74 { 75 if(a[i+1][j]) Hash(id,val); 76 if(a[i][j+1]) Hash(id-bit[j-1]*lef+bit[j]*lef,val); 77 } 78 else if((lef==1)&&(up==1)) 79 { 80 int s=1; 81 for (R k=j+1;k<=m;++k) 82 { 83 if((id>>(2*k))%4==1) s++; 84 if((id>>(2*k))%4==2) s--; 85 if(s==0) 86 { 87 Hash(id-bit[j-1]-bit[j]-bit[k],val); 88 break; 89 } 90 } 91 } 92 else if((lef==2)&&(up==2)) 93 { 94 int s=1; 95 for (R k=j-2;k>=0;--k) 96 { 97 if((id>>(2*k))%4==1) s--; 98 if((id>>(2*k))%4==2) s++; 99 if(s==0) 100 { 101 Hash(id-2*bit[j-1]-2*bit[j]+bit[k],val); 102 break; 103 } 104 } 105 } 106 else if((lef==2)&&(up==1)) 107 { 108 Hash(id-lef*bit[j-1]-up*bit[j],val); 109 } 110 else if((lef==1)&&(up==2)) 111 { 112 if(i==tx&&j==ty) ans+=val; 113 } 114 } 115 } 116 } 117 printf("%lld",ans); 118 return 0; 119 }