http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=22385
给出一个 n∗m 大小的棋盘,棋盘上有空格也有障碍物,求经过所有非障碍格子的曼哈顿回路个数。
可以用插头DP解决此题。
插头DP中的轮廓线 S 的 f 值就代表了轮廓线上的插头状态为 S ,并且经过了被DP遍历过的所有格子的不完全曼哈顿回路个数
我这里定义的不完全曼哈顿回路指的是不封闭的一个回路。
DP过程就是从上到下,一行一行地进行遍历,格子 (i,j) 的每个轮廓线状态都是由 (i,j−1) (若 j=1 ,就是 (i−1,m) )的所有合法前驱状态转移而来。
在DP到格子 (i,j) 时进行下面的分类讨论
一、 (i,j) 是空格
1、移动拐角前的轮廓线, (i,j) 左边的插头和上边的插头都不为空
不管这两个插头是否相同,都将它们合并。
2、移动拐角前的轮廓线, (i,j) 左边的插头和上边的插头中有一个为空,设不为空的那个插头为 t
若 (i,j) 右边的格子为空格,那么可以将轮廓线状态进行这样的转移:右移拐角后,新的左插头为移动前的 t 。(相当于将联通块向右延伸成L型)
若 (i,j) 下边的格子为空格,那么可以将轮廓线状态进行这样的转移:右移拐角后,新的下插头为移动前的 t 。(相当于将联通块向下延伸成|型)
二、 (i,j) 是障碍物
移动轮廓线拐角后, (i,j) 下面的插头和 (i,j) 右边的插头全部清空。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 15 //棋盘大小
#define MOD 30007 //hash模数
#define STATUS 1000010 //状态数组的大小
using namespace std;
typedef long long int LL;
int map[MAXN][MAXN];
int code[MAXN]; //解码后的插头序列
int ch[MAXN]; //最小表示法使用,ch[i]
int ex,ey; //最后一个非障碍格子坐标
struct HashMap
{
int head[MOD],next[STATUS],size;
LL status[STATUS]; //每种状态的轮廓线
LL f[STATUS]; //每种状态对应的DP值f
void init()
{
size=0;
memset(head,-1,sizeof(head));
}
void push(LL sta,LL ans) //将轮廓线状态为sta,DP值为ans的状态结点插入到hash表中
{
int tmp=sta%MOD;
for(int p=head[tmp];p!=-1;p=next[p])
if(status[p]==sta)
{
f[p]+=ans;
return;
}
status[size]=sta;
f[size]=ans;
next[size]=head[tmp];
head[tmp]=size++;
}
}hm[2]; //此处用了滚动数组
void decode(int *code,int m,LL status) //将三进制数status转换为长度为m+1的轮廓线code
{
for(int i=m;i>=0;i--)
{
code[i]=status&7;
status>>=3; //!!!!!!
}
}
LL encode(int *code,int m) //将长度为m+1的轮廓线转换为三进制状态
{
int cnt=1;
memset(ch,-1,sizeof(ch));
ch[0]=0; //!!!!!!
LL status=0; //最终返回的状态
for(int i=0;i<=m;i++)
{
if(ch[code[i]]==-1) ch[code[i]]=cnt++;
code[i]=ch[code[i]];
status<<=3;
status|=code[i];
}
return status;
}
void shift(int *code,int m) //轮廓线换行
{
for(int i=m;i>0;i--) code[i]=code[i-1]; //!!!!!
code[0]=0;
}
int n,m; //n*m大小的棋盘
void dpblank(int i,int j,int cur) //空格格子的DP
{
for(int k=0;k<hm[cur].size;k++)
{
decode(code,m,hm[cur].status[k]);
int left=code[j-1];
int up=code[j]; //左插头和上插头
if(left&&up) //左插头和上插头均不为空
{
if(left==up) //两个插头连的是同一联通块,加入(i,j)后会形成一个环
{
if(i==ex&&j==ey)
{
code[j-1]=code[j]=0;
if(j==m) shift(code,m);
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
}
else //两个联通块连的不是同一联通块,加入当前格子(i,j)后两个联通块会连在一起
{
code[j-1]=code[j]=0;
for(int t=0;t<=m;t++)
if(code[t]==up)
code[t]=left;
if(j==m)
shift(code,m);
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
}
else if((left&&(!up))||((!left)&&up)) //左插头和上插头中有一个为空
{
int tmp; //tmp=左插头和上插头中不为空的那个插头
if(left) tmp=left;
else tmp=up;
if(map[i][j+1])
{
code[j-1]=0;
code[j]=tmp;
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
if(map[i+1][j])
{
code[j-1]=tmp;
code[j]=0;
if(j==m) shift(code,m);
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
}
else //左、上插头均为空,加入(i,j)后会形成新联通块
{
if(map[i][j+1]&&map[i+1][j]) //(i,j)下面和右边的格子均不为空
{
code[j-1]=code[j]=13;
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
}
}
}
void dpblock(int i,int j,int cur) //(i,j)是障碍物的DP
{
for(int k=0;k<hm[cur].size;k++)
{
decode(code,m,hm[cur].status[k]);
code[j-1]=code[j]=0; //加入(i,j)后,清空(i,j)位置下面和右边的插头
if(j==m) shift(code,m);
hm[cur^1].push(encode(code,m),hm[cur].f[k]);
}
}
void init()
{
char s[MAXN];
memset(map,0,sizeof(map));
ex=0,ey=0;
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=m;j++)
{
if(s[j]=='.')
{
ex=i,ey=j;
map[i][j]=1;
}
}
}
}
void solve() //DP过程
{
int cur=0;
LL ans=0;
hm[cur].init();
hm[cur].push(0,1); //初始的DP边界状态:整个棋盘都没访问过,DP值为1
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
hm[cur^1].init(); //!!!!
if(map[i][j])
dpblank(i,j,cur);
else
dpblock(i,j,cur);
cur^=1;
}
for(int i=0;i<hm[cur].size;i++)
ans+=hm[cur].f[i];
printf("%lld\n",ans);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
if(ex==0) //特判无解
{
printf("0\n");
continue;
}
solve();
}
return 0;
}