bzoj1814 Ural 1519 Formula 1(插头dp模板题)

1814: Ural 1519 Formula 1

Time Limit: 1 Sec  Memory Limit: 64 MB
Submit: 924  Solved: 351
[Submit][Status][Discuss]

Description

 一个 m * n 的棋盘,有的格子存在障碍,求经过所有非障碍格子的哈密顿回路个数

Input

The first line contains the integer numbers N and M (2 ≤ N, M ≤ 12). Each of the next N lines contains M characters, which are the corresponding cells of the rectangle. Character "." (full stop) means a cell, where a segment of the race circuit should be built, and character "*" (asterisk) - a cell, where a gopher hole is located.

Output

You should output the desired number of ways. It is guaranteed, that it does not exceed 2^63-1.

Sample Input

4 4
**..
....
....
....

Sample Output

2
分析:今天把插头dp学了一下,没想到dp还能写这么长......细节什么的也很多,在这里当一个模板吧.
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;

const int maxn = 30010;
const int pow[13] = {0,2,4,6,8,10,12,14,16,18,20,22,24};
int n,m,now,pre,tx,ty;
char map[20][20];

struct node
{
    int head[maxn],nextt[maxn],tot;
    ll sum[maxn],sta[maxn];
    void clear()
    {
        memset(head,-1,sizeof(head));
        tot = 0;
    }
    void push(ll x,ll v)
    {
        ll hashh = x % maxn;
        for (int i = head[hashh]; i >= 0; i = nextt[i])
        {
            if (sta[i] == x)
            {
                sum[i] += v;
                return;
            }
        }
        sta[tot] = x;
        sum[tot] = v;
        nextt[tot] = head[hashh];
        head[hashh] = tot++;
    }
} f[2];

int turnleft(ll x,int k)
{
    return x << pow[k];
}

int get(ll x,int k)
{
    return (x >> pow[k]) & 3;
}

ll del(ll x,int i,int j)
{
    return x & (~(3 << pow[i])) & (~(3 << pow[j]));
}

int findr(ll x,int pos)
{
    int cnt = 1;
    for (int i = pos + 1; i <= m; i++)
    {
        int k = get(x,i);
        if (k == 1)
            cnt++;
        else if (k == 2)
            cnt--;
        if (!cnt)
            return i;
    }
}

int findl(ll x,int pos)
{
    int cnt = 1;
    for (int i = pos - 1; i >= 0; i--)
    {
        int k = get(x,i);
        if (k == 2)
            cnt++;
        else if (k == 1)
            cnt--;
        if (!cnt)
            return i;
    }
}

void solve2(int x,int y,int k)
{
    int p = get(f[pre].sta[k],y - 1);   //右插头
    int q = get(f[pre].sta[k],y);   //下插头
    ll staa = del(f[pre].sta[k],y - 1,y);   //将这两个插头删掉以后的状态
    ll v = f[pre].sum[k];
    if (!p && !q)  //新建一个连通分量
    {
        if (map[x][y] == '*')
        {
            f[now].push(staa,v);
            return;
        }
        if (x < n && y < n && map[x + 1][y] == '.' && map[x][y + 1] == '.')
            f[now].push(staa | turnleft(1,y - 1) | turnleft(2,y),v);
    }
    else if (!p || !q)  //保持原来的连通分量
    {
        int temp = p + q;
        if (x < n && map[x + 1][y] == '.')
            f[now].push(staa | turnleft(temp,y - 1),v);
        if (y < m && map[x][y + 1] == '.')
            f[now].push(staa | turnleft(temp,y),v);
    }
    else if (p == 1 && q == 1)    //连接两个联通分量
        f[now].push(staa ^ turnleft(3,findr(staa,y)),v);  //这里的异或实际上就是把1变成2,2变成1
    else if (p == 2 && q == 2)
        f[now].push(staa ^ turnleft(3,findl(staa,y - 1)),v);
    else if (p == 2 && q == 1)
        f[now].push(staa,v);
    else if (x == tx && y == ty)
        f[now].push(staa,v);
}

ll solve()
{
    f[0].clear();
    f[0].push(0,1);
    now = 0,pre = 1;  //滚动数组
    for (int i = 1; i <= n; i++)
    {
        pre = now;
        now ^= 1;
        f[now].clear();
        for (int k = 0; k < f[pre].tot; k++)
            f[now].push(turnleft(f[pre].sta[k],1),f[pre].sum[k]);   //左移一位,因为轮廓线下来的时候会少一个插头
        for (int j = 1; j <= m; j++)
        {
            pre = now;
            now ^= 1;
            f[now].clear();
            for (int k = 0; k < f[pre].tot; k++)
                solve2(i,j,k);  //处理第k个状态
        }
    }
    for (int i = 0; i < f[now].tot; i++)
        if (f[now].sta[i] == 0)  //没有插头了.
            return f[now].sum[i];
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++)
        scanf("%s",map[i] + 1);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (map[i][j] == '.')
                tx = i,ty = j; //找右下角的非障碍点
    if (!tx)
        puts("0");
    else
        printf("%lld\n",solve());

    return 0;
}

 

posted @ 2018-02-21 20:24  zbtrs  阅读(203)  评论(0编辑  收藏  举报