您的位置:首页 > 其它

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

2018-02-21 20:24 453 查看

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: