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;
}