这两天研究了下插头dp。总算懂了一点点= =。
插头dp主要用于解决数据规模小的棋盘模型路径问题。
首推CDQ的国家队论文。很详细好懂。
http://wenku.baidu.com/view/4fe4ac659b6648d7c1c74633.html
现在就以hdu 1693这道入门题来讲一下最基础的插头dp吧。
题意:
给定一个n*m的矩阵。其中有一些方块可以走。另一些不能走。问将原图划为几个哈密顿回路。使之覆盖所有可以走的点的方案数目。(简单说就是用几个路径不相交的环覆盖所有点)。下图就有三种方案。
在做题之前。先明确两个概念。插头和轮廓线。
插头:
某个插头存在。就是指插头所指向的格子和当前格子之间有路径存在。比如说。左下图中A格子的上插头存在。就是指右下图的情况。
那么我们就有了描述这些环的方法。
轮廓线:
红色格子为已决策部分。白色未决策部分。中间的蓝线就是轮廓线。它是插头dp的关键。注意轮廓线长度一般为(m+1)。
接下来讨论状态的表示和转移。
我们将轮廓线上的插头状态压缩。比如下图可表示成二进制111001011(注意转弯处也要表示)
那么用dp[i][j][k]表示决策到第(i,j)个格子。状态为k时的方案数。
状态的转移?
根据当前的i,j,k。我们可以得到当前决策格的上方和右方的插头情况。
例如上图。i=3,j=5,k=111001011。则上插头存在。左插头不存在。则要么存在下插头。要么存在右插头。(如下图)
其他的情况同样进行分类讨论。行首及行末进行特判。不可行走的格子同样特判。这里不再赘述= =。论文里已经很清楚了。
最后贴代码= =。
#include<iostream>
#include<cstdio>
#include<cstring>
#define mk(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;
const int N=14;
int n,m;
struct ctdpman
{
ll f[N][N][1<<N];
int map[N][N],T;
void read()//初始化= =
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&map[i][j]);
mk(f);
f[1][1][0]=1;
}
int go(int y,int S,int v) // 状态转移函数 '^':上 'v':下 '<':左 '>':右 ' ':无插头
{
if(y==1)//特判行首
{
if(v==1)return (S<<1)^3; //('^' to 'v ')
if(v==2)return (S<<1); //('^' to ' >')
return (S<<1)|3; //(' ' to 'v>')
}
if(v==1)return S^(3<<(y-1));//('<^' to ' ') or (' ' to 'v>') or ('< ' to ' >') or (' ^' to 'v ')
return S; //('< ' to 'v ') or (' ^' to ' >')
}
void dpit()
{
int topk=(1<<(m+1)); //状态上限
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<=topk;k++)
{
if(!f[i][j][k])continue;
bool a=k&(1<<(j-1)),b=k&(1<<j);
if(!map[i][j])
{
if((!a && !b) || (j==1 &&!a))
{
if(j==m)f[i+1][1][k]+=f[i][j][k];
else f[i][j+1][j==1?k<<1:k]+=f[i][j][k]; //这里要注意转移状态= =。坑了我半个小时
}
}
else
{
if(j==m)//特判行末
{
if(b)
f[i+1][1][go(j,k,1)]+=f[i][j][k];
if(a && !b)
f[i+1][1][go(j,k,2)]+=f[i][j][k];
}
else if(j==1)//特判行首
{
if(a)
{
f[i][j+1][go(j,k,1)]+=f[i][j][k];
f[i][j+1][go(j,k,2)]+=f[i][j][k];
}
else f[i][j+1][go(j,k,3)]+=f[i][j][k];
}
else//一般情况
{
f[i][j+1][go(j,k,1)]+=f[i][j][k];
if(a^b) f[i][j+1][go(j,k,2)]+=f[i][j][k];
}
}
}
}
void work()
{
read();
dpit();
printf("Case %d: There are %lld ways to eat the trees.\n",T,f[n+1][1][0]);
}
}d;
int main()
{
scanf("%d",&d.T);
int i=d.T;
for(d.T=1;d.T<=i;d.T++)
d.work();
return 0;
}