pd头插
用途
插头dp主要是用来解决基于连通性状态压缩的动态规划问题,一般来说,就是解决一个网格图中的回路方案数的问题,并且数据范围较小,比如这道模板
方法
定义插头:路径是否经过格点的边,如图就是一个左插头
不难发现,对于一个回路上的所有点,都有且只有两个插头,如图
于是我会爆搜\(\Theta(6^{nm})\)
这显然不现实,考虑dp转移
转移的对象
考虑转移轮廓线,也就是说一条将我们已经考虑的半图和未考虑的半图分开的线,如图中绿线,蓝线表示已经考虑
通常来说,我们采取逐格递推(少数情况下,我们会考虑逐行递推)
状态的表示
最小表示法
障碍为0,第一个连通块内编号为1,第二个连通块内编号为2……
(还有一种是将每个连通块标记成最左边的列编号)
括号表示法
轮廓线上从左到右4个插头 a, b, c, d,如果 a, c连通,并且与 b 不连通,那么 b, d 一定不连通。(对所有棋盘问题都适用,详见CDQ论文)
那么不难联想到括号匹配,用0表示无括号,1,表示左括号,2表示右括号(实现中一般采用4进制,位运算真香)
一些trival的细节
表示编码一般考虑用map或hash滚动dp,要不然内存会爆
大致方式
就是分类疯累讨论,一个格子只有上下,左右,上左,上右,下左,下右6种情况,于是讨论就完了,贴上模板的代码
#include<bits/stdc++.h>
using namespace std;
namespace DEBUG {
void debug_out() { cerr << '\n'; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) { cerr << ' ' << H, debug_out(T...); }
#define debug(...) cerr << '[' << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
} using namespace DEBUG;
typedef long long ll;
typedef unsigned long long ull;
//#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++)
namespace get_out
{
char B[1<<15],*S=B,*T=B;
char op;
inline void read_(int &x)
{
x=0;
int fi(1);
op=getchar();
while((op<'0'||op>'9')&&op!='-') op=getchar();
if(op=='-') op=getchar(),fi=-1;
while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
x*=fi;
return;
}
inline void read_(long long &x)
{
x=0;
int fi(1);
op=getchar();
while((op<'0'||op>'9')&&op!='-') op=getchar();
if(op=='-') op=getchar(),fi=-1;
while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
x*=fi;
return;
}
inline void read_(double &x)
{
x=0.0;
float fi(1.0),sm(0.0);
op=getchar();
while((op<'0'||op>'9')&&op!='-') op=getchar();
if(op=='-') op=getchar(),fi=-1.0;
while(op>='0'&&op<='9') x=(x*10.0)+(op^48),op=getchar();
if(op=='.') op=getchar();
int rtim=0;
while(op>='0'&&op<='9') sm=(sm*10.0)+(op^48),++rtim,op=getchar();
while(rtim--) sm/=10.0;
x+=sm,x*=fi;
return;
}
inline void postive_write(int x)
{
if(x>9) postive_write(x/10);
putchar(x%10|'0');
}
inline void postive_write(long long x)
{
if(x>9) postive_write(x/10);
putchar(x%10|'0');
}
inline void write_(int x)
{
if(x<0) x=-x,putchar('-');
postive_write(x);
}
inline void write_(int x,char ch)
{
write_(x),putchar(ch);
}
inline void write_(long long x)
{
if(x<0) x=-x,putchar('-');
postive_write(x);
}
inline void write_(long long x,char ch)
{
write_(x),putchar(ch);
}
}
using get_out::read_;
using get_out::write_;
#define maxn 15
#define inf 0x7f7f7f7f
#define mod 299993
#define hashMaxNum 300000
int n,m,head[hashMaxNum];
int cnt[2],mp[maxn][maxn],edi,edj,now,la;
ll ans=0;
class hahaha
{
public:
int s[20];
hahaha()=default;
hahaha(int state)
{
s[0]=state&3;
for(int i=1;i<=m;++i) s[i]=(state>>(i<<1))&3;
}
inline int rar()
{
int state=0;
for(int i=1;i<=m;++i) state|=s[i]<<(i<<1);
return (state|s[0]);
}
};
inline hahaha unpack(int state) {return hahaha(state);}
inline int rar(hahaha &H){return H.rar();}
class hash_T
{
public:
int state[2],nex;
ll num[2];
hash_T()=default;
}s[hashMaxNum];
inline void insert(int state,ll num)
{
int tmp=state%mod;
for(int i=head[tmp];i;i=s[i].nex)
if(state==s[i].state[now])
{
s[i].num[now]+=num;
return;
}
s[++cnt[now]].state[now]=state,
s[cnt[now]].nex=head[tmp],
s[head[tmp]=cnt[now]].num[now]=num;
}
hahaha now_state,_k;
ll lnum;
inline void zip_insert()
{
insert(rar(_k),lnum),_k=now_state;
}
inline void solve()
{
register int i,j,k;
int west,north;
insert(0,1);
for(i=1;i<=n;++i) for(j=1;j<=m;++j)
{
la=now,now^=1;//renew state
cnt[now]=0,memset(head,0,sizeof(head));//init
for(k=1;k<=cnt[la];++k)
{
now_state=unpack(s[k].state[la]),_k=now_state;
lnum=s[k].num[la],west=now_state.s[0],north=now_state.s[j];
if(!mp[i][j]) {if(!west&&!north) insert(rar(_k),lnum);continue;}
if(!west&&!north)
{
if(mp[i][j+1]&&mp[i+1][j])
_k.s[0]=2,
_k.s[j]=1,//挂过
zip_insert();
continue;
}
if(!west&&north)
{
if(mp[i+1][j])
insert(rar(_k),lnum);
if(mp[i][j+1])
_k.s[0]=north,
_k.s[j]=0,
zip_insert();
continue;
}
if(west&&!north)
{
if(mp[i][j+1])
insert(rar(_k),lnum);
if(mp[i+1][j])
_k.s[0]=0,
_k.s[j]=west,
zip_insert();
continue;
}
if(west==1&&north==2)
{
_k.s[0]=_k.s[j]=0;
bool flag=0;
for(int pos=0;pos<=m;++pos)
if(_k.s[pos])
{
flag=1;
break;
}
if(!flag&&i==edi&&j==edj) ans+=lnum;
continue;
}
if(west==2&&north==1)
{
_k.s[0]=_k.s[j]=0;
zip_insert();
continue;
}
if(west==1&&north==1)
{
int nm=1,pos=j+1;
for(;pos<=m;++pos)
{
nm+=_k.s[pos]==1?1:(_k.s[pos]==2?-1:0);
if(!nm) break;
}
_k.s[pos]=1;
_k.s[0]=_k.s[j]=0;
zip_insert();
continue;
}
if(west==2&&north==2)
{
int nm=-1,pos=j-1;
for(;pos;--pos)
{
nm+=_k.s[pos]==1?1:(_k.s[pos]==2?-1:0);
if(!nm) break;
}
_k.s[pos]=2;
_k.s[0]=_k.s[j]=0;
zip_insert();
continue;
}
}
}
}
signed main()
{
register char ch[20];
read_(n),read_(m);
for(int i=1;i<=n;++i)
{
scanf("%s",ch+1);
for(int j=1;j<=m;++j)
((ch[j]=='.'&&(mp[i][j]=1,edi=i,edj=j))||(mp[i][j]=0));
}
solve();
cout<<ans<<'\n';
return 0;
}