[省选前题目整理][URAL 1519]Formula 1(插头DP)

题目链接

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=22385

题目大意

给出一个 nm 大小的棋盘,棋盘上有空格也有障碍物,求经过所有非障碍格子的曼哈顿回路个数。

思路

可以用插头DP解决此题。

插头DP中的轮廓线 S f 值就代表了轮廓线上的插头状态为 S ,并且经过了被DP遍历过的所有格子的不完全曼哈顿回路个数

我这里定义的不完全曼哈顿回路指的是不封闭的一个回路。

DP过程就是从上到下,一行一行地进行遍历,格子 (i,j) 的每个轮廓线状态都是由 (i,j1) (若 j=1 ,就是 (i1,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;
}

你可能感兴趣的:([省选前题目整理][URAL 1519]Formula 1(插头DP))