hdu 1693 eat the trees----插头dp入门

这两天研究了下插头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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值