FZU1977 Pandora adventure —— 插头DP
题目链接:https://vjudge.net/problem/FZU-1977
Problem 1977 Pandora adventureAccept: 597 Submit: 2199
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description
The pollution of the earth is so serious that people can not survive any more. Fortunately, people have found a new planet that maybe has life, and we call it "Pandora Planet".
Leonardo Da Vinci is the only astronaut on the earth. He will be sent to the Pandora Planet to gather some plant specimens and go back. The plant specimen is important to the people to decide whether the planet is fit to live or not.
Assuming that Da Vinci can only move in an N×M grid. The positions of the plant specimens he wants to collect are all marked by the satellite. His task is to find a path to collect all the plant specimens and return to the spaceship. There are some savage beasts in the planet. Da Vinci can not investigate the grid with the savage beast. These grids are also marked by the satellite. In order to save time Da Vinci could only visit each grid exactly once and also return to the start grid, that is, you can not visit a grid twice except the start grid. You should note that you can choose any grid as the start grid.
Now he wants to know the number of different paths he can collect all the plant specimens. We only care about the path and ignore where the start grid is, so the two paths in Figure 1 are considered as the same.
Figure 1
Input
The first line of the input contains an integer T (T≤100), indicating the number of cases. Each case begins with a line containing two integers N and M (1≤N, M≤12), the size of the planet is N×M. Each of the following N lines contains M characters Gij(1≤i≤N, 1≤j≤M), Gij denotes the status of the grid in row i and column j, where 'X' denotes the grid with savage beast, '*' denotes the safe grid that you can decide to go or not, 'O' denotes the plant specimen you should collect. We guarantee that there are at least three plant specimens in the map.
Output
For each test case, print a line containing the test case number (beginning with 1) and the number of different paths he can collect all the plant specimens. You can make sure that the answer will fit in a 64-bit signed integer.
Sample Input
2 2
OO
O*
4 4
***O
XO**
**O*
XX**
Sample Output
Case 2: 7
Source
The 35th ACM/ICPC Asia Regional Fuzhou Site —— Online Contest
题意:
给出一个n*m(n、m<=12)的棋盘,‘X’表示不可走格子, ‘*’表示可选格子, ‘O’表示必走格子,问有多少种回路,使得回路经过所有必走格刚好一次?
题解:
1.此题(URAL1519 Formula 1)的加强版,多了“可选格子”。
2.怎么处理这个“可选格子”呢?答:
1) 对于当前格子,如果他是可选格子,且没有左、上插头。由于他是“可选”的,那么我们就可以走出两个分支:一是作为必走格子新建连通分量,而是作为不可走格子,直接转移到下一个格子。
2) 由于可选的格子加入,就不能像以往记录最后一个必走格子来结束回路了,因为在必走格子之后,可能还有可选格子,而可选格子同样可以作为结束回路的格子。
3) 为了解决如何结束回路的问题,给出了两种解决方案,两种方案都需要添加一个标记isend来记录是否已经形成回路。
方案一:
对于可走的格子(可选或者必走),如果它有左、上插头,且这两个插头属于同一个分量,那么我们就把他们接上(不管当前格子是否为最后一个必走格),形成了一个回路。然后,我们再去判断是否还有其他连通分量,如果有的话,那么这种方案肯定不合法,应当舍弃;如果没有其他连通分量,那么我们就暂且当它是合法的,再接下来的过程中,如果发现还有必走点,那么这种方案也就被发现是不合法的,所以也应当抛弃。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int MOD = 1e9+;
const int MAXN = 1e5;
const int HASH = 1e4; int n, m;
int maze[][]; struct
{
int size, head[HASH], next[MAXN];
LL state[MAXN], sum[MAXN]; void init()
{
size = ;
memset(head, -, sizeof(head));
} void insert(LL status, LL Sum)
{
int u = status%HASH;
for(int i = head[u]; i!=-; i = next[i])
{
if(state[i]==status)
{
sum[i] += Sum;
return;
}
}
state[size] = status; //头插法
sum[size] = Sum;
next[size] = head[u];
head[u] = size++;
} }Hash_map[]; struct
{
int code[]; //用于记录轮廓线上每个位置的插头状态
int isend; //标记是否已经结束,即形成回路
LL encode(int m) //编码:把轮廓线上的信息压缩到一个longlong类型中
{
LL status = isend;
int id[], cnt = ;
memset(id, -, sizeof(id));
id[] = ;
for(int i = m; i>=; i--) //从高位到低位。为每个连通块重新编号,采用最小表示法。
{
if(id[code[i]]==-) id[code[i]] = ++cnt;
code[i] = id[code[i]];
status <<= ; //编码
status += code[i];
}
return status;
} void decode(int m, LL status) //解码:将longlong类型中轮廓线上的信息解码到数组中
{
memset(code, , sizeof(code));
for(int i = ; i<=m; i++) //从低位到高位
{
code[i] = status&;
status >>= ;
}
isend = status&;
} void shift(int m) //左移:在每次转行的时候都需要执行。
{
for(int i = m-; i>=; i--)
code[i+] = code[i];
code[] = ;
} }Line; void transfer_blank(int i, int j, int cur)
{
for(int k = ; k<Hash_map[cur].size; k++) //枚举上一个格子所有合法的状态
{
LL status = Hash_map[cur].state[k]; //得到状态
LL Sum = Hash_map[cur].sum[k]; //得到数量
Line.decode(m, status); //对状态进行解码
int up = Line.code[j]; //得到上插头
int left = Line.code[j-]; //得到下插头 if(Line.isend)
{
if(maze[i][j]==) continue; //如果已经结束了,但在后面却出现了必走格,说明这种情况非法。
Line.code[j] = Line.code[j-] = ; //否则,这个格就是可选格,在结束后就视为不可走格,直接转移
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
continue;
} if(!up && !left) //没有上、左插头,新建分量
{
if(maze[i+][j] && maze[i][j+]) //如果新建的两个插头所指向的两个格子可行,新建的分量才合法
{
Line.code[j] = Line.code[j-] = ; //为新的分量编号,最大的状态才为6
Hash_map[cur^].insert(Line.encode(m), Sum);
}
if(maze[i][j]==) //如果为可选点,那么在没有插头的时候,可以选择不走
{
Line.code[j] = Line.code[j-] = ;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
else if( (left&&!up) || (!left&&up) ) //仅有其中一个插头,延续分量
{
int line = left?left:up; //记录是哪一个插头
if(maze[i+][j]) //往下延伸
{
Line.code[j-] = line;
Line.code[j] = ;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
if(maze[i][j+]) //往右延伸
{
Line.code[j-] = ;
Line.code[j] = line;
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
else //上、左插头都存在,尝试合并。
{
if(up!=left) //如果两个插头属于两个联通分量,那么就合并
{
Line.code[j] = Line.code[j-] = ;
for(int t = ; t<=m; t++) //随便选一个编号最为他们合并后分量的编号
if(Line.code[t]==up)
Line.code[t] = left;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
else //由于不确定哪个是结束格,所以就不再根据结束格来判断,而是暂且认为这种情况合法,到后面才排除非法的
{
Line.code[j] = Line.code[j-] = ;
if(j==m) Line.shift(m);
Line.isend = ;
for(int t = ; t<=m; t++) //合并后看看是否只有一个连通分量
if(Line.code[t])
Line.isend = ;
if(Line.isend)
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
}
} void transfer_block(int i, int j, int cur)
{
for(int k = ; k<Hash_map[cur].size; k++)
{
LL status = Hash_map[cur].state[k]; //得到状态
LL Sum = Hash_map[cur].sum[k]; //得到数量
Line.decode(m, status);
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
} int main()
{
int T, kase = ;
scanf("%d", &T);
while(T--)
{
char s[];
scanf("%d%d", &n, &m);
memset(maze, false, sizeof(maze));
for(int i = ; i<=n; i++)
{
scanf("%s", s+);
for(int j = ; j<=m; j++)
{
if(s[j]=='X') maze[i][j] = ;
else if(s[j]=='O') maze[i][j] = ;
else maze[i][j] = ;
}
} int cur = ;
Hash_map[cur].init();
Hash_map[cur].insert(, );
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
{
Hash_map[cur^].init();
if(!maze[i][j])
transfer_block(i, j ,cur);
else
transfer_blank(i, j, cur);
cur ^= ;
} LL last_status = ;
LL ans = Hash_map[cur].size?Hash_map[cur].sum[last_status]:;
printf("Case %d: %I64d\n", ++kase, ans);
}
}
方案二:
跟方案一类似,只不过把 “在形成回路后,如果发现了必走点,就舍弃” 这个判断提前了。即:在形成回路的时候,既要判断是否还有其他连通分量,也要判断后面是否还有必走格。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int MOD = 1e9+;
const int MAXN = 1e5;
const int HASH = 1e4; int n, m, last_x, last_y;
int maze[][];
bool hav[][]; struct
{
int size, head[HASH], next[MAXN];
LL state[MAXN], sum[MAXN]; void init()
{
size = ;
memset(head, -, sizeof(head));
} void insert(LL status, LL Sum)
{
int u = status%HASH;
for(int i = head[u]; i!=-; i = next[i])
{
if(state[i]==status)
{
sum[i] += Sum;
return;
}
}
state[size] = status; //头插法
sum[size] = Sum;
next[size] = head[u];
head[u] = size++;
} }Hash_map[]; struct
{
int code[]; //用于记录轮廓线上每个位置的插头状态
int isend;
LL encode(int m) //编码:把轮廓线上的信息压缩到一个longlong类型中
{
LL status = isend;
int id[], cnt = ;
memset(id, -, sizeof(id));
id[] = ;
for(int i = m; i>=; i--) //从高位到低位。为每个连通块重新编号,采用最小表示法。
{
if(id[code[i]]==-) id[code[i]] = ++cnt;
code[i] = id[code[i]];
status <<= ; //编码
status += code[i];
}
return status;
} void decode(int m, LL status) //解码:将longlong类型中轮廓线上的信息解码到数组中
{
memset(code, , sizeof(code));
for(int i = ; i<=m; i++) //从低位到高位
{
code[i] = status&;
status >>= ;
}
isend = status&;
} void shift(int m) //左移:在每次转行的时候都需要执行。
{
for(int i = m-; i>=; i--)
code[i+] = code[i];
code[] = ;
} }Line; void transfer_blank(int i, int j, int cur)
{
for(int k = ; k<Hash_map[cur].size; k++) //枚举上一个格子所有合法的状态
{
LL status = Hash_map[cur].state[k]; //得到状态
LL Sum = Hash_map[cur].sum[k]; //得到数量
Line.decode(m, status); //对状态进行解码
int up = Line.code[j]; //得到上插头
int left = Line.code[j-]; //得到下插头 if(Line.isend) //如果已经结束了,那么说明这个格是可选格,在此视为不可走格,直接转移到下一个格
{
Line.code[j] = Line.code[j-] = ;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
continue;
} if(!up && !left) //没有上、左插头,新建分量
{
if(maze[i+][j] && maze[i][j+]) //如果新建的两个插头所指向的两个格子可行,新建的分量才合法
{
Line.code[j] = Line.code[j-] = ; //为新的分量编号,最大的状态才为6
Hash_map[cur^].insert(Line.encode(m), Sum);
}
if(maze[i][j]==)
{
Line.code[j] = Line.code[j-] = ;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
else if( (left&&!up) || (!left&&up) ) //仅有其中一个插头,延续分量
{
int line = left?left:up; //记录是哪一个插头
if(maze[i+][j]) //往下延伸
{
Line.code[j-] = line;
Line.code[j] = ;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
if(maze[i][j+]) //往右延伸
{
Line.code[j-] = ;
Line.code[j] = line;
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
else //上、左插头都存在,尝试合并。
{
if(up!=left) //如果两个插头属于两个联通分量,那么就合并
{
Line.code[j] = Line.code[j-] = ;
for(int t = ; t<=m; t++) //随便选一个编号最为他们合并后分量的编号
if(Line.code[t]==up)
Line.code[t] = left;
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
else if(!hav[i][j]) //后面没有必走格子
{
Line.code[j] = Line.code[j-] = ;
if(j==m) Line.shift(m);
Line.isend = ;
for(int t = ; t<=m; t++) //合并后看看是否只有一个连通分量
if(Line.code[t])
Line.isend = ;
if(Line.isend)
Hash_map[cur^].insert(Line.encode(m), Sum);
}
}
}
} void transfer_block(int i, int j, int cur)
{
for(int k = ; k<Hash_map[cur].size; k++)
{
LL status = Hash_map[cur].state[k]; //得到状态
LL Sum = Hash_map[cur].sum[k]; //得到数量
Line.decode(m, status);
if(j==m) Line.shift(m);
Hash_map[cur^].insert(Line.encode(m), Sum);
}
} int main()
{
int T, kase = ;
scanf("%d", &T);
while(T--)
{
char s[];
scanf("%d%d", &n, &m);
memset(maze, false, sizeof(maze));
for(int i = ; i<=n; i++)
{
scanf("%s", s+);
for(int j = ; j<=m; j++)
{
if(s[j]=='X') maze[i][j] = ;
else if(s[j]=='*') maze[i][j] = ;
else
{
maze[i][j] = ;
last_x = i;
last_y = j;
}
}
}
memset(hav, false, sizeof(hav));
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
if(i<last_x || (i==last_x&&j<last_y))
hav[i][j] = true; int cur = ;
Hash_map[cur].init();
Hash_map[cur].insert(, );
for(int i = ; i<=n; i++)
for(int j = ; j<=m; j++)
{
Hash_map[cur^].init();
if(!maze[i][j])
transfer_block(i, j ,cur);
else
transfer_blank(i, j, cur);
cur ^= ;
} LL last_status = ;
LL ans = Hash_map[cur].size?Hash_map[cur].sum[last_status]:;
printf("Case %d: %I64d\n", ++kase, ans);
}
}
错误分析:
一开始的时候,我是是这样写的:
if(!maze[i][j])
transfer_block(i, j ,cur);
else if(maze[i][j]==)
transfer_blank(i, j, cur);
else
{
transfer_block(i, j ,cur);
transfer_blank(i, j, cur);
}
即:如果当前格子是可选格子,那么就可以分两个分支进行转移。
但是统计数会偏多,因为:
1.在上一个格子转移的时候,因为把当前格子看作是可走格子,所以就可以有插头引向当前格子。然后到了转移这个格子的时候,如果把当前格子看做是不可走格子,那么它的前提是没有插头引过来。然后在上一个格子转移的时候,就既有插头的情况,也有没有的情况,所以数量肯定偏大。解决方法是:把当前格子当成不可走格子时,加个判断,判断是否含有插头。
2.在形成回路之后,所以的可选格子一律看成是不可走格子,所以只需一个分支去转移到下一个格子,两个分支肯定会出现重复,使得数量偏大。
FZU1977 Pandora adventure —— 插头DP的更多相关文章
- [FZU1977] Pandora adventure
来学插头DP了= = GDKOI前觉得不会考数位DP,GDOI前觉得插头DP用不上.. 结果令人伤感>_< 这题并不用增加状态.. 只要在形成环的时候,让形成环的位置在最后一个必走点之后, ...
- 插头DP专题
建议入门的人先看cd琦的<基于连通性状态压缩的动态规划问题>.事半功倍. 插头DP其实是比较久以前听说的一个东西,当初是水了几道水题,最近打算温习一下,顺便看下能否入门之类. 插头DP建议 ...
- 插头dp练习
最近学了插头dp,准备陆续更新插头dp类练习. 学习论文还是cdq那篇<基于连通性状态压缩的动态规划问题>. 基本的想法都讲得很通透了,接下来就靠自己yy了. 还有感谢kuangbin大大 ...
- HDU 3377 Plan (插头DP,变形)
题意:有一个n*m的矩阵,每个格子中有一个值(可能负值),要从左上角走到右下角,求路径的最大花费. 思路: 除了起点和终点外,其他的点可以走,也可以不走. (2)我用的是括号表示法,所以起始状态为') ...
- 初探插头dp
开学那个月学了点新东西,不知道还记不记得了,mark一下 感觉cdq的论文讲的很详细 题主要跟着kuangbin巨做了几道基础的 http://www.cnblogs.com/kuangbin/arc ...
- 插头dp
插头dp 感受: 我觉得重点是理解,算法并不是直接想出怎样由一种方案变成另一种方案.而是方案本来就在那里,我们只是枚举状态统计了答案. 看看cdq的讲义什么的,一开始可能觉得状态很多,但其实灰常简单 ...
- HDU 4113 Construct the Great Wall(插头dp)
好久没做插头dp的样子,一开始以为这题是插头,状压,插头,状压,插头,状压,插头,状压,无限对又错. 昨天看到的这题. 百度之后发现没有人发题解,hust也没,hdu也没discuss...在acm- ...
- HDU 4949 Light(插头dp、位运算)
比赛的时候没看题,赛后看题觉得比赛看到应该可以敲的,敲了之后发现还真就会卡题.. 因为写完之后,无限TLE... 直到后来用位运算代替了我插头dp常用的decode.encode.shift三个函数以 ...
- HDU 1693 Eat the Trees(插头DP、棋盘哈密顿回路数)+ URAL 1519 Formula 1(插头DP、棋盘哈密顿单回路数)
插头DP基础题的样子...输入N,M<=11,以及N*M的01矩阵,0(1)表示有(无)障碍物.输出哈密顿回路(可以多回路)方案数... 看了个ppt,画了下图...感觉还是挺有效的... 参考 ...
随机推荐
- 理解 Nova 架构
Compute Service Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源. OpenStack 作为 IaaS 的云操作系统,虚拟机生命周期管理也就是通过 Nov ...
- 系统进程的Watchdog
编写者:李文栋 /rayleeya http://rayleeya.iteye.com/blog/1963408 3.1 Watchdog简介 对于像笔者这样没玩过硬件的纯软程序员来说,第一次看到这个 ...
- 在C#中使用Json.Net进行序列化和反序列化及定制化
序列化(Serialize)是将对象转换成字节流,并将其用于存储或传输的过程,主要用途是保存对象的状态,以便在需要时重新创建该对象:反序列化(Deserialize)则是将上面的字节流转换为相应对象的 ...
- php 处理大文件方法 SplFileObject
$csv_file = 'tmp.csv'; $start = isset($_GET['start']) ?intval($_GET['start']) : 1; // 从第几行开始读取 $num ...
- Protostuff序列化和反序列化使用说明
原文:http://blog.csdn.net/zhglance/article/details/56017926 google原生的protobuffer使用起来相当麻烦,首先要写.proto文件, ...
- android 播放MP3
<?xml version="1.0" encoding="utf-8"?> <!-- 定义当前布局的基本LinearLayout --> ...
- JS---数组(Array)处理函数整理
1.concat() 连接两个或更多的数组该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本.例如: 代码如下: <script type="text/javascript&q ...
- 校园网、教育网 如何纯粹访问 IPv6 网站避免收费
我国校园网有可靠的 IPv6 网络环境,速度非常快.稳定,并且大多数高校在网络流量计费时不会限制 IPv6 的流量,也就是免费的.然而访问 IPv4 商业网络时,则会收费,并且连接的可靠性一般.可幸的 ...
- 转:为什么Uber宣布从Postgres切换到MySQL?
转: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653547609&idx=1&sn=cbb55ee823dd ...
- Java经常使用类及其经常用法
1.ArrayList java.util.ArrayList<E> add(E e) //插入尾部 add(int index, E element) remove(int index) ...