bzoj1814 Ural 1519 Formula 1(插头dp模板题)
1814: Ural 1519 Formula 1
Time Limit: 1 Sec Memory Limit: 64 MBSubmit: 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; }