HDU1043
八数码问题,搜索必做题之一吧,做法很多种目前只会两种,由于这道题最后都是到达同一种状态,所以可以反向离线打表出所有的情况不超过40W种 bfs + 康托展开 + 路径记录。这里路径记录值得一提的是我们以步数少,字典序小为前提。打印的时候是以当前输入的序列为出发点像树根节点回溯打印。所以反向打表的时候记录路径的应该逆过来记录。
//逆向bfs打表,目标终点状态一样从目标开始打表出不到40W种的所有情况
//从起点状态str1回溯目标状态。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int sum[10] = {1,1,2,6,24,120,720,5040,40320,362880};
int dx[] = {0,1,-1,0};
int dy[] = {-1,0,0,1};
char dir[5] = {"dlru"};
struct info{
char way; //记录方法
int pre; //记录前驱
}w[370000];
struct Node{
int ct; //康托值
int state[9]; //状态
int num; //9的位置
};
queue<Node>q;
int Cantor(int s[],int n) //康托展开判重
{
int result = 0;
for(int i = 0;i < n;i++){
int cnt = 0;
for(int j = i+1;j < n;j++){
if(s[i] > s[j]){
cnt++;
}
}
result += cnt * sum[n-i-1];
}
return result + 1;
}
void bfs()
{
while(!q.empty()){
Node ptr = q.front(),p;
q.pop();
for(int i = 0;i < 4;i++){
int nx = ptr.num % 3 + dx[i]; //转化为二维图数字9的交换后横坐标
int ny = ptr.num / 3 + dy[i]; //转化为二维图数字9的交换后纵坐标
int nz = nx + 3*ny; //算出新的一维为坐标
if(nx < 0 || nx >= 3 || ny < 0 || ny >= 3){ //边界检查
continue;
}
memcpy(&p,&ptr,sizeof(struct Node)); //结构体赋值
p.num = nz; //9的新位置
swap(p.state[nz],p.state[ptr.num]); //新老位置交换
p.ct = Cantor(p.state,9); //计算康托值
if(w[p.ct].pre == -1){ //判断该康托值是否之前就拓展过,先拓展的一定是最小的
w[p.ct].pre = ptr.ct; //连接当前状态的上一个状态
w[p.ct].way = dir[i];
q.push(p);
}
}
}
}
void slove()
{
while(!q.empty()){
q.pop();
}
int a[9] = {1,2,3,4,5,6,7,8,9};
Node p;
for(int i = 0;i < 370000;i++){
w[i].pre = -1;
}
memcpy(p.state,a,sizeof(p.state)); //状态图复制
p.ct = 0; //初始康托值为0
w[p.ct].pre = 0; //前驱为0,此时这是树根位置
p.num = 8; //初始位置
q.push(p);
bfs(); //预处理所有的状态
}
int main()
{
slove(); //预处理
char s[100];
while(gets(s)){
int len = strlen(s);
int t[9],j = 0;
for(int i = 0;i < len;i++){
if(s[i] >= '1' && s[i] <= '8'){
t[j++] = s[i] - '0';
}
else if(s[i] == 'x'){
t[j++] = 9;
}
}
int ans = Cantor(t,9);
if(w[ans].pre == -1){
printf("unsolvable\n");
}
else{
while(ans){
printf("%c",w[ans].way); //打印方法
ans = w[ans].pre; //向前回溯
}
printf("\n");
}
}
return 0;
}
//以目标节点为中心,拓展出一棵树,每个节点都是一种状态。
A*搜索
A*搜索是启发式搜索的一种,相对于普通bfs搜索加入了优先队列以及一个估计函数h,通过从搜索出发点到当前点的消耗g,以及当前节点到达目标节点的估计值h,通过估计值和当前消耗f = g+h,再借助优先队列小顶堆的优势,每次优先拓展理论上离目标更近的节点。这样搜索出来的结果肯定是最优的而且比是普通广搜快的。难点就是h函数怎么去设计,理论上h函数没有一个严格的设计标准,只要能够加快搜索进度的h函数都是可以的。只是优劣之分。
迷宫问题A*搜索的h函数
第一种是曼哈顿估计,当前点到目标节点横坐标差值的绝对值加上纵坐标差值的绝对值
第二种是欧式距离,当前点与目标节点对角线的值
八数码问题A*搜索的h函数
第一种,每个数字到对应位置需要移动的格子数目
第二种,有多少个在对应位置的个数
其实Astar搜索无非就是bfs搜索的拓展,只不过加入了估价函数,对于这个估价函数呢你也可以加入自己的思路去把它设计的更完美,让搜索跑的更快。当然Astar搜索对于普通bfs搜索的优点就是在有解的情况下更快,如果题目存在无解的情况那基本上没啥大的提升,反正这两种搜索都会搜索完所有的可能情况,只不过先后顺序可能不一样罢了,如果题目没有无解的情况或者你能够把无解的情况单独拿出来剪枝那么使用Astar能够加快搜索,如果无解的情况你剪枝不掉或者剪枝掉意义不大那么普通广搜和Astar理论上差不多。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 370000;
int sum[10] = {1,1,2,6,24,120,720,5040,40320,362880};
bool visited[maxn];
int ed[] = {1,2,3,4,5,6,7,8,9};
int dx[] = {0,-1,1,0};
int dy[] = {1,0,0,-1};
char dir[5] = {"dlru"};
struct info{
int state[10];
int g,f,h,num;
char way[50];
bool operator <(const info s)const{
if(s.f == f){
return s.g < g;
}
else{
return s.f < f;
}
}
};
int Cantor(int s[]) //康托展开
{
int result = 0;
for(int i = 0;i < 9;i++){
int cnt = 0;
for(int j = i+1;j < 9;j++){
if(s[i] > s[j]){
cnt++;
}
}
result += cnt*sum[8-i];
}
return result + 1;
}
int Manhaton(int st[]) //曼哈顿估计,每一个数字归位需要多少步
{
int result = 0;
for(int i = 0;i < 9;i++){
int num = st[i] - 1;
int x = i/3;
int y = i%3;
int a = num/3;
int b = num%3;
result += abs(x-a) + abs(y-b);
}
return result;
}
priority_queue<info>q;
int Astar()
{
while(!q.empty()){
info ptr = q.top(),p;
q.pop();
for(int i = 0;i < 4;i++){
int nx = ptr.num%3 + dx[i];
int ny = ptr.num/3 + dy[i];
int nz = nx + 3*ny;
if(nx < 0 || nx >= 3 || ny < 0 || ny >= 3){
continue;
}
memcpy(&p,&ptr,sizeof(struct info));
swap(p.state[ptr.num],p.state[nz]);
p.num = nz;
int ct = Cantor(p.state);
if(visited[ct]){ //判重
continue;
}
p.way[ptr.g] = dir[i];
p.g = ptr.g + 1;
p.h = Manhaton(p.state);
p.f = p.g + p.h;
if(p.h == 0){ //目标状态
p.way[p.g] = '\0';
printf("%s\n",p.way);
return p.g;
}
q.push(p);
visited[ct] = true;
}
}
return -1;
}
int main()
{
char s[100];
while(gets(s)){
int len = strlen(s);
int t[10],j = 0,p_x = 0;
for(int i = 0;i < len;i++){
if(s[i] == 'x'){
p_x = j;
t[j++] = 9;
continue;
}
else if(s[i] >= '1' && s[i] <= '8'){
t[j++] = s[i] - '0';
}
}
int sum = 0;
for(int i = 0;i < j;i++){
if(t[i] == 9){
continue;
}
for(int k = 0;k < i;k++){
if(t[k] == 9){
continue;
}
if(t[i] < t[k]){
sum++;
}
}
}
memset(visited,false,sizeof(visited));
while(!q.empty()){
q.pop();
}
info p;
memcpy(p.state,t,sizeof(t));
p.g = 0;p.h = Manhaton(t);
if(p.h == 0){
printf("\n");
continue;
}
if(sum % 2 == 1){ //剪枝掉无解的情况,不然还是会超时。
printf("unsolvable\n");
continue;
}
p.f = p.h + p.g;p.num = p_x;
p_x = Cantor(p.state);
visited[p_x] = true;
q.push(p);
Astar();
}
return 0;
}
/*
234 123 1+1+3
159 456 1+0+1
768 789 0+2+1
*/
HDU3567
八数码升级,指定初始序列和目标序列,求出最短的步骤,字典序最小。还是离线打表,不过这次打表需要打出大约370万种情况。还需要结合hash映射。bfs打表 + 康托展开 + hash
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int sum[10] = {1,1,2,6,24,120,720,5040,40320,362880};
int dx[] = {0,-1,1,0};
int dy[] = {1,0,0,-1};
char dir[5] = "dlru";
char s[10][10] = {"012345678", "102345678", "120345678", "123045678",
"123405678","123450678", "123456078", "123456708", "123456780"};
struct node{
int pre;
char way;
int step;
}Node[370000][11];
struct info{
int ct;
char state[10];
int num;
};
queue<info>q;
int Cantor(char s[])
{
int result = 0;
for(int i = 0;i < 9;i++){
int cnt = 0;
for(int j = i+1;j < 9;j++){
if(s[i] > s[j]){
cnt++;
}
}
result += cnt * sum[8-i];
}
return result + 1;
}
void bfs(int x)
{
while(!q.empty()){
info ptr = q.front(),p;
q.pop();
for(int i = 0;i < 4;i++){
int nx = ptr.num%3 + dx[i];
int ny = ptr.num/3 + dy[i];
int nz = nx + 3*ny;
if(nx < 0 || nx >= 3 || ny < 0 || ny >= 3){
continue;
}
memcpy(p.state,ptr.state,sizeof(p.state));
swap(p.state[nz],p.state[ptr.num]);
p.num = nz;
p.ct = Cantor(p.state);
if(Node[p.ct][x].pre == -1){
Node[p.ct][x].step = Node[ptr.ct][x].step + 1;
Node[p.ct][x].pre = ptr.ct;
Node[p.ct][x].way = dir[i];
q.push(p);
}
}
}
}
void clear_queue(int j)
{
while(!q.empty()){
q.pop();
}
for(int i = 0;i < 370000;i++){
Node[i][j].pre = -1;
}
}
void slove()
{
for(int i = 0;i < 9;i++){
clear_queue(i);
info p;
memcpy(p.state,s[i],sizeof(p.state));
p.ct = Cantor(s[i]);
p.num = i;
Node[p.ct][i].step = 0;
Node[p.ct][i].pre = 0;
q.push(p);
bfs(i);
}
}
int main()
{
// freopen("C:\\Users\\Administrator\\Desktop\\input.txt","r",stdin);
// freopen("C:\\Users\\Administrator\\Desktop\\output.txt","w",stdout);
slove();
int t;
scanf("%d",&t);
for(int j = 1;j <= t;j++){
char str1[100],str2[100];
int pos;
scanf("%s%s",str1,str2);
for(int i = 0;i < 9;i++){
if(str1[i] == 'X'){
str1[i] = '0';
pos = i;
}
if(str2[i] == 'X'){
str2[i] = '0';
}
}
int hash[10];
for(int i = 0;i < 9;i++){
hash[str1[i] - '0'] = s[pos][i] - '0';
}
for(int i = 0;i < 9;i++){
str2[i] = hash[str2[i] - '0'] + '0';
}
str2[9] = '\0';
int ctvalue = Cantor(str2);
int k = Node[ctvalue][pos].step;
printf("Case %d: %d\n",j,k);
//printf("%c",Node[ctvalue][pos].way);
int p = k;
char ans[50];
while(ctvalue){
ans[--k] = Node[ctvalue][pos].way;
ctvalue = Node[ctvalue][pos].pre;
}
ans[p] = '\0';
printf("%s\n",ans);
}
return 0;
}
HDU3533
预处理 + 思维 + A*或者普通bfs也可以,还是不够老道没做过的情况下很难想到这么做。不过最后弄明白了就好,毕竟是学习嘛。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 105;
bool map[maxn][maxn];
bool visited[maxn][maxn][1005];
bool t[maxn][maxn][1005];
int m,n,k,d;
int dx[] = {-1,1,0,0,0};
int dy[] = {0,0,-1,1,0};
struct point{
int x,y,t,v,dir;
}s[maxn];
struct Node{
int x,y;
int f,g,h;
bool operator<(const Node a)const{
if(a.f == f){
return a.g < g;
}
else{
return a.f < f;
}
}
};
priority_queue<Node>q;
void clear_set()
{
memset(map,false,sizeof(map));
memset(visited,false,sizeof(visited));
memset(t,false,sizeof(t));
while(!q.empty()){
q.pop();
}
}
void pre_set()
{
for(int i = 0;i < k;i++){ //城堡数
for(int j = 0;j <= d;j += s[i].t){ //时间
int p = 1;
while(true){ //模拟一下子弹,子弹是有周期射出的。
int x = s[i].x + dx[s[i].dir]*p;
int y = s[i].y + dy[s[i].dir]*p;
if(x < 0 || x > m || y < 0 || y > n || map[x][y]){
break;
}
if(p % s[i].v == 0){
t[x][y][j+p/s[i].v] = true;
}
p++;
}
}
}
}
int Astar()
{
while(!q.empty()){
Node ptr = q.top(),p;
q.pop();
if(ptr.g > d){
return -1;
}
if(ptr.x == m && ptr.y == n){
return ptr.g;
}
for(int i = 0;i < 5;i++){
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
p.g = ptr.g + 1;
if(p.x < 0 || p.x > m || p.y < 0 || p.y > n){
continue;
}
if(map[p.x][p.y] || visited[p.x][p.y][p.g] || t[p.x][p.y][p.g]){
continue;
}
p.h = abs(p.x - m) + abs(p.y - n);
p.f = p.g + p.h;
visited[p.x][p.y][p.g] = true;
q.push(p);
}
}
return -1;
}
int main()
{
while(~scanf("%d%d%d%d",&m,&n,&k,&d)){
clear_set();
for(int i = 0;i < k;i++){
char c[5];
scanf("%s%d%d%d%d",c,&s[i].t,&s[i].v,&s[i].x,&s[i].y);
if(c[0] == 'N') s[i].dir = 0;
if(c[0] == 'S') s[i].dir = 1;
if(c[0] == 'W') s[i].dir = 2;
if(c[0] == 'E') s[i].dir = 3;
map[s[i].x][s[i].y] = true;
}
pre_set();
Node p;
p.x = 0;p.y = 0;p.g = 0;
p.h = m+n;p.f = p.h+p.g;
visited[0][0][0] = true;
q.push(p);
int ans = Astar();
if(ans == -1){
printf("Bad luck!\n");
}
else{
printf("%d\n",ans);
}
}
return 0;
}
HDU1254
经典推箱子游戏,不要注意箱子怎么懂,更多的应该关注人能不能走到箱子位置上,如果人能走到箱子上说明上一步是在箱子周围,这时候在顺手把箱子往人移动的方向推动一格。bfs + 优先队列 + 状态压缩,人走动箱子没动也要进行状态标记。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 10;
struct info{
int x,y;
int bx,by;
int s;
bool operator <(const info &a)const{
return a.s < s;
}
};
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
int map[maxn][maxn];
bool visited[maxn][maxn][maxn][maxn]; //前面两个维度是人的坐标,后面两个维度是箱子坐标
int m,n;
priority_queue<info>q;
void clear_set()
{
memset(visited,false,sizeof(visited));
while(!q.empty()){
q.pop();
}
}
int bfs()
{
info ptr,p;
while(!q.empty()){
ptr = q.top();
q.pop();
if(map[ptr.bx][ptr.by] == 3){
return ptr.s;
}
for(int i = 0;i < 4;i++){
memcpy(&p,&ptr,sizeof(info));
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
if(p.x < 0 || p.x >= m || p.y < 0 || p.y >= n || map[p.x][p.y] == 1){
continue;
}
if(p.x == ptr.bx && p.y == ptr.by){ //人在箱子上,则上一步在箱子周围
p.bx = p.x + dx[i];
p.by = p.y + dy[i];
if(p.bx < 0 || p.bx >= m || p.by < 0 || p.by >= n || map[p.bx][p.by] == 1){
continue;
}
p.s = ptr.s + 1;
}
if(visited[p.x][p.y][p.bx][p.by] == false){
visited[p.x][p.y][p.bx][p.by] = true;
q.push(p);
}
}
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
clear_set();
scanf("%d%d",&m,&n);
info p;
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
scanf("%d",&map[i][j]);
if(map[i][j] == 2){
p.bx = i;p.by = j;
map[i][j] = 0;
}
if(map[i][j] == 4){
p.x = i;p.y = j;
map[i][j] = 0;
}
}
}
p.s = 0;
visited[p.x][p.y][p.bx][p.by] = true;
q.push(p);
int ans = bfs();
printf("%d\n",ans);
}
return 0;
}
HDU1429
走迷宫,不过这个迷宫有门有钥匙,需要先拿钥匙才能开门。难点就在如何处理这个拿钥匙又要开门了,不过是真的佩服各位大佬。开始做的时候是结构体里带一个钥匙数组标记有没有拿钥匙,出发点很好,最后发现自己无法对状态进行标记了,为啥不能标记了,因为我拿钥匙需要往回走,你不能标记点同时你还得拿钥匙然后就陷入了死循环出不来了,所以就需要在这二者之间去一个平衡点来进行状态标记。看完解题报告瞬间就目标咋做了。首先是十把钥匙,换成二进制就是1024 = 2^10,刚好。然后呢每一位代表一个钥匙,也叫二进制状态压缩。
000000001 A钥匙 1<<0
000000010 B钥匙 1<<1
000000100 C钥匙 1<<2
000001000 D钥匙 1<<3
1111111111 所有钥匙都有,此时这个二进制转成十进制就是1023
钥匙合并怎么合并呢,二进制的基本操作(|)操作就能实现。这是获得钥匙
钥匙能不能开门就看,当前这个字母和钥匙值总和进行(&)运算。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
struct info{
int x,y;
int k,s; //s是步数,k是所有钥匙的十进制表示
};
const int maxn = 25;
char map[maxn][maxn];
bool visited[maxn][maxn][1025]; //一二维是坐标,三维是当前钥匙状态对点的状态标记
int n,m,t;
queue<info>q;
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
void clear_set()
{
memset(visited,false,sizeof(visited));
while(!q.empty()){
q.pop();
}
}
int bfs()
{
info ptr,p;
while(!q.empty()){
ptr = q.front();
q.pop();
if(ptr.s >= t){
continue;
}
if(map[ptr.x][ptr.y] == '^'){
return ptr.s;
}
for(int i = 0;i < 4;i++){
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
p.s = ptr.s + 1;
p.k = ptr.k;
if(p.x < 0 || p.x >= n || p.y < 0 || p.y >= m || map[p.x][p.y] == '*' || visited[p.x][p.y][p.k]){
continue;
}
if(map[p.x][p.y] >= 'a' && map[p.x][p.y] <= 'j'){
int index = map[p.x][p.y] - 'a';
p.k = p.k|(1<<index); //钥匙合并
}
if(map[p.x][p.y] >= 'A' && map[p.x][p.y] <= 'J'){
int index = map[p.x][p.y] - 'A';
if((p.k&(1<<index)) == 0){ //不能打开门的条件
continue; //打开门的条件(p.k&(1<<index)) == (1<<index)
} //注意区分
}
visited[p.x][p.y][p.k] = true;
q.push(p);
}
}
return -1;
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&t)){
clear_set();
info p;
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
cin>>map[i][j];
if(map[i][j] == '@'){
p.x = i;p.y = j;
map[i][j] = '.';
}
}
}
p.s = 0;p.k = 0;
q.push(p);
int ans = bfs();
printf("%d\n",ans);
}
return 0;
}
HDU2102
bfs + queue,这个题比较简单,无非就是两个地图。判断一下走的是哪个地图。开一个三位状态标记数组判重就好了。穿越的时候判断一下这个点是不是#,或者这个点是墙都是不可取的,如果穿过去还是#就死循环了,然后搜索就完事了。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
using namespace std;
struct info{
int x,y;
int k,s;
};
const int maxn = 15;
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
int n,m,k;
bool visited[3][maxn][maxn];
char map[3][maxn][maxn];
queue<info>q;
void clear_set()
{
memset(visited,false,sizeof(visited));
while(!q.empty()){
q.pop();
}
}
inline bool check(info p) //基本检查
{
if(p.x >= 0 && p.x < n && p.y >= 0 && p.y < m
&& map[p.k][p.x][p.y] != '*'
&& !visited[p.k][p.x][p.y]){
return true;
}
else{
return false;
}
}
int bfs()
{
info ptr,p;
while(!q.empty()){
ptr = q.front();
q.pop();
if(ptr.s > k){
continue;
}
if(map[ptr.k][ptr.x][ptr.y] == 'P'){
return ptr.s;
}
for(int i = 0;i < 4;i++){
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
p.k = ptr.k;
if(check(p)){
if(map[p.k][p.x][p.y] == '#'){
info t;
memcpy(&t,&p,sizeof(info)); //拷贝基本信息
t.k = (p.k+1)%2; //是哪一张图
if(check(t) && map[t.k][t.x][t.y] != '#'){ //检查穿越过去的位置
visited[p.k][p.x][p.y] = true; //穿越入口标记
p.k = (p.k+1)%2;
p.s = ptr.s + 1;
q.push(p);
visited[t.k][p.x][p.y] = true; //穿越出口标记
}
}
else{
p.s = ptr.s + 1;
visited[p.k][p.x][p.y] = true;
q.push(p);
}
}
}
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&k);
clear_set();
info p;
for(int i = 0;i < n;i++){
scanf("%s",map[0][i]);
for(int j = 0;j < m;j++){
if(map[0][i][j] == 'S'){
p.k = 0;p.x = i;p.y = j;
p.s = 0;
}
}
}
for(int i = 0;i < n;i++){
scanf("%s",map[1][i]);
for(int j = 0;j < m;j++){
if(map[1][i][j] == 'S'){
p.k = 1;p.x = i;p.y = j;
p.s = 0;
}
}
}
visited[p.k][p.x][p.y] = true;
q.push(p);
int ans = bfs();
if(ans == -1){
printf("NO\n");
}
else{
printf("YES\n");
}
}
return 0;
}
双向广搜的第一道题,虽然很早就知道有这么个玩意,思路也很清晰但是不怎么用,可能是大部分常见的题目都能单向搜过去吧。这道题的技巧还是很多的,最重要的是怎么对棋盘状态判重这个很重要,其次就是棋子有四个,然后四个棋子应该怎么样来判重?很多想到的是开八维数组是没错,但是四个棋子全排列有很多种坐标。这里就需要排序处理,按棋子坐标升序依次往八维数组里面放。其实也就是通过排序hash了,你也可以对四个棋子的全排列顺序进行标记。不过写的代码会很多。最好的标记就是按照棋子升序标记,这样就不会很麻烦。tbfs + queue + hash + sort排序,思路就是正向拓展四次,反向拓展
四次,如果两个范围跑出来的圆圈能产生交点那么一定可以,如果正反bfs跑出来的范围没有交点代表无法实现,这样就能避免内存消耗太大已经时间不理想的情况。细节看代码~~~~
//Accepted 1401 62MS 18264K 3657 B G++
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
struct point{
int x,y;
};
struct info{
point a[4];
int step;
}s,e;
bool map[10][10];
char v[8][8][8][8][8][8][8][8];
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
queue<info>q[2];
void clear_set() //清空设置
{
memset(v,false,sizeof(v));
while(!q[0].empty()){
q[0].pop();
}
while(!q[1].empty()){
q[1].pop();
}
memset(v,0,sizeof(v));
}
bool cmp(point s1,point s2) //排序规则
{
if(s1.x == s2.x){
return s1.y < s2.y;
}
else{
return s1.x < s2.x;
}
}
inline void set_map(info p)
{
map[p.a[0].x][p.a[0].y] = true;
map[p.a[1].x][p.a[1].y] = true;
map[p.a[2].x][p.a[2].y] = true;
map[p.a[3].x][p.a[3].y] = true;
}
inline void set_visited(info p,char x) //设置标记
{
v[p.a[0].x][p.a[0].y][p.a[1].x][p.a[1].y][p.a[2].x][p.a[2].y][p.a[3].x][p.a[3].y] = x;
}
inline char get_visited(info p) //获得标记值
{
return v[p.a[0].x][p.a[0].y][p.a[1].x][p.a[1].y][p.a[2].x][p.a[2].y][p.a[3].x][p.a[3].y];
}
inline bool check(int x,int y) //边界检查
{
if(x >= 0 && x < 8 && y >=0 && y < 8){
return true;
}
else{
return false;
}
}
int tbfs()
{
set_visited(s,'1');
set_visited(e,'2');
s.step = 0;e.step = 0;
q[0].push(s);q[1].push(e);
info ptr,str;
while(!q[0].empty() || !q[1].empty()){
if(!q[0].empty()){
ptr = q[0].front();q[0].pop();
if(ptr.step >= 4){ //大于等于4步不再拓展
continue;
}
memset(map,false,sizeof(map));
set_map(ptr);
for(int i = 0;i < 4;i++){
for(int j = 0;j < 4;j++){
info p;
memcpy(&p,&ptr,sizeof(info));
p.a[i].x = ptr.a[i].x + dx[j];
p.a[i].y = ptr.a[i].y + dy[j];
if(!check(p.a[i].x,p.a[i].y)){ //走出边界跳掉
continue;
}
if(map[p.a[i].x][p.a[i].y]){ //相邻点不能走通,进行跳跃
p.a[i].x += dx[j];
p.a[i].y += dy[j];
if(!check(p.a[i].x,p.a[i].y)){ //跳跃之后检查边界
continue;
}
}
sort(p.a,p.a+4,cmp);
if(get_visited(p) == '2'){
return 1;
}
else if(get_visited(p) == '1'){
continue;
}
p.step = ptr.step + 1;
set_visited(p,'1');
q[0].push(p);
}
}
}
if(!q[1].empty()){
str = q[1].front();
q[1].pop();
if(str.step >= 4){
continue;
}
memset(map,false,sizeof(map));
set_map(str);
for(int i = 0;i < 4;i++){
for(int j = 0;j < 4;j++){
info p;
memcpy(&p,&str,sizeof(info));
p.a[i].x = str.a[i].x + dx[j];
p.a[i].y = str.a[i].y + dy[j];
if(!check(p.a[i].x,p.a[i].y)){
continue;
}
if(map[p.a[i].x][p.a[i].y]){
p.a[i].x += dx[j];
p.a[i].y += dy[j];
if(!check(p.a[i].x,p.a[i].y)){
continue;
}
}
sort(p.a,p.a+4,cmp);
if(get_visited(p) == '1'){
return 1;
}
else if(get_visited(p) == '2'){
continue;
}
set_visited(p,'2');
p.step = str.step + 1;
q[1].push(p);
}
}
}
}
return -1;
}
int main()
{
int x,y;
while(~scanf("%d%d",&x,&y)){
clear_set();
s.a[0].x = x-1;s.a[0].y = y-1;
for(int i = 1;i < 4;i++){
scanf("%d%d",&x,&y);
s.a[i].x = x-1;s.a[i].y = y-1;
}
for(int i = 0;i < 4;i++){
scanf("%d%d",&x,&y);
e.a[i].x = x-1;e.a[i].y = y-1;
}
sort(s.a,s.a+4,cmp);
sort(e.a,e.a+4,cmp);
int ans = tbfs();
if(ans == -1){
puts("NO");
}
else{
puts("YES");
}
}
return 0;
}
HDU1430
类似与八数码问题,但是操作只有三种,找好转移状态。
bfs打表 + queue + 康托展开 + hash。不打表的话会超时。我写的Astar超时了。我感觉上迭代加深也是一样会超时,做法应该就是预打表 + hash处理。记得写hash前洗把脸~
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 45000;
int sum[] = {1,1,2,6,24,120,720,5040,40320,362880};
struct node{
char way;
int pre,s;
}Node[maxn];
struct info{
int state[8];
int ct;
};
bool visited[maxn];
queue<info>q;
int Cantor(int s[])
{
int result = 0;
for(int i = 0;i < 8;i++){
int cnt = 0;
for(int j = i+1;j < 8;j++){
if(s[i] > s[j]){
cnt++;
}
}
result += cnt*sum[7-i];
}
return result + 1;
}
void bfs()
{
info ptr,p;
while(!q.empty()){
ptr = q.front();
q.pop();
for(int i = 0;i < 3;i++){
memcpy(&p,&ptr,sizeof(info));
if(i == 0){
for(int j = 7;j >= 0;j--){
p.state[j] = ptr.state[7-j];
}
}
if(i == 1){
for(int j = 0;j < 4;j++){
p.state[j] = ptr.state[(j+3)%4];
}
for(int j = 4;j < 7;j++){
p.state[j] = ptr.state[j+1];
}
p.state[7] = ptr.state[4];
}
if(i == 2){
int t = p.state[6];
p.state[6] = p.state[5];
p.state[5] = p.state[2];
p.state[2] = p.state[1];
p.state[1] = t;
}
p.ct = Cantor(p.state);
if(Node[p.ct].pre == -1){
Node[p.ct].s = Node[ptr.ct].s + 1;
Node[p.ct].pre = ptr.ct;
Node[p.ct].way = 'A' + i;
q.push(p);
}
}
}
}
void clear_set()
{
for(int i = 0;i < maxn;i++){
Node[i].pre = -1;
}
while(!q.empty()){
q.pop();
}
info p;
for(int i = 0;i < 8;i++){
p.state[i] = i+1;
}
p.ct = Cantor(p.state);
Node[p.ct].pre = 0;
Node[p.ct].s = 0;
q.push(p);
bfs();
}
int main()
{
clear_set();
char s1[100],s2[100];
while(~scanf("%s%s",s1,s2)){
int hash[10] = {0};
for(int i = 0;i < 8;i++){
hash[s1[i] - '0'] = i + 1;
}
int t[10];
for(int i = 0;i < 8;i++){
t[i] = hash[s2[i] - '0'];
}
int k = Cantor(t),cnt = Node[k].s;
char str[100];
str[cnt] = '\0';
while(Node[k].pre){
str[--cnt] = Node[k].way;
k = Node[k].pre;
}
puts(str);
}
return 0;
}
HDU1226
比较坑的一道题,卡了我好几个小时不知道错在哪,最后改了一下判断顺序就过了。难点就在于一个取余判断,需要采用大数模板。以及怎么去重,肯定的极限压缩一下空间,500位的密码不可能用map或者set去重这样必定超内存。然后是技巧标记。好题啊,最开始我也没想到这样标记状态~
bfs + queue + 大数取余 + 技巧
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
struct info{
int t;
char s[550];
};
queue<info>q;
bool visited[5500];
char str[20];
int n,c,m;
void clear_set()
{
memset(str,0,sizeof(str));
memset(visited,false,sizeof(visited));
while(!q.empty()){
q.pop();
}
}
int bigmod(char s[],int len) //大数取余运算
{
int div = 0;
for(int i = 0;i < len;i++){
int sum;
if(s[i] >= 'A' && s[i] <= 'F'){
sum = div * c + ((int)s[i] - 55);
}
else if(s[i] >= '0' && s[i] <= '9'){
sum = div * c + s[i] - '0';
}
div = sum % n;
}
return div;
}
void bfs()
{
info ptr,p;
while(!q.empty()){
ptr = q.front();
q.pop();
if(ptr.t >= 500){ //这里有没有都一样。
break;
}
for(int i = 0;i < m;i++){
memcpy(&p,&ptr,sizeof(info));
p.s[ptr.t] = str[i];
p.t = ptr.t + 1;
int div = bigmod(p.s,p.t);
if(div == 0){
p.s[p.t] = '\0';
printf("%s\n",p.s);
return ;
}
if(visited[div] == false && p.t < 500){ //对余数进行标记
q.push(p);
visited[div] = true;
}
}
}
printf("give me the bomb please\n");
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
clear_set();
scanf("%d%d%d",&n,&c,&m);
for(int i = 0;i < m;i++){
char s1[5];
scanf("%s",s1);
str[i] = s1[0];
}
sort(str,str+m); //排序
if(n == 0){ //0的特殊处理
if(str[0] == '0'){
printf("0\n");
}
else{
printf("give me the bomb please\n");
}
continue;
}
int flag = 1;
for(int i = 0;i < m;i++){
info p;
p.s[0] = str[i];p.t = 1;
if(p.s[0] != '0'){ //第一位不能为0
int div = bigmod(p.s,p.t);
if(div == 0){
p.s[p.t] = '\0';
printf("%s\n",p.s);
flag = 0;
break;
}
q.push(p);
visited[div] = true;
}
}
if(flag){
bfs();
}
}
return 0;
}
HDU1044
和上面那个拿钥匙走迷宫的题差不多,同样是二进制压缩状态。
bfs + queue + 二进制状态压缩 + 寻找最大值。
//500ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 55;
struct info{
int x,y,k;
int step,cost;
};
char map[maxn][maxn];
bool visited[maxn][maxn][1025];
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
int s[12];
int m,n,t,sum;
queue<info>q;
void clear_set()
{
memset(visited,false,sizeof(visited));
memset(s,0,sizeof(s));
while(!q.empty()){
q.pop();
}
}
inline bool check(int x,int y,int k)
{
if(x < 0 || x >= n || y < 0 || y >= m){
return true;
}
if(map[x][y] == '*'){
return true;
}
return false;
}
int bfs()
{
info ptr,p;
int cnt = -1;
while(!q.empty()){
ptr = q.front();
q.pop();
if(ptr.step > t){
break;
}
if(map[ptr.x][ptr.y] == '<'){
cnt = max(ptr.cost,cnt);
}
for(int i = 0;i < 4;i++){
memcpy(&p,&ptr,sizeof(info));
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
if(check(p.x,p.y,p.k)){
continue;
}
p.step = ptr.step + 1;
if(map[p.x][p.y] >= 'A' && map[p.x][p.y] <= 'J'){
int sum = map[p.x][p.y] - 'A';
if((p.k&(1<<sum))== 0){ //没有这个判断会一直WA,WA了五次看题解发现的
p.cost += s[sum];
p.k = p.k|(1<<sum);
}
}
if(visited[p.x][p.y][p.k] == false){
visited[p.x][p.y][p.k] = true;
q.push(p);
}
}
}
return cnt;
}
int main()
{
int cnt;
scanf("%d",&cnt);
for(int k = 1;k <= cnt;k++){
clear_set();
scanf("%d%d%d%d",&m,&n,&t,&sum);
for(int i = 0;i < sum;i++){
scanf("%d",&s[i]);
}
info p;
for(int i = 0;i < n;i++){
scanf("%s",map[i]);
for(int j = 0;j < m;j++){
if(map[i][j] == '@'){
p.x = i;p.y = j;
}
}
}
p.cost = 0;p.step = 0;p.k = 0;
visited[p.x][p.y][p.k] = true;
q.push(p);
int ans = bfs();
printf("Case %d:\n",k);
if(ans == -1){
printf("Impossible\n");
}
else{
printf("The best score is %d.\n",ans);
}
if(k != cnt){
printf("\n");
}
}
return 0;
}
/*
4
4 4 2 2
100 200
****
*@A*
*B<*
****
4 4 1 2
100 200
****
*@A*
*B<*
****
12 5 13 2
100 200
************
*B.........*
*.********.*
*@...A....<*
************
12 5 30 2
100 200
************
*B.........*
*.********.*
*@...A....<*
************
*/
HDU3085
双向广搜题目,有很多需要处理的细节。
首先是人鬼距离处理,采用曼哈顿距离处理,只要人和鬼的距离大于2倍的当前时间人就安全,我最开始是预打表鬼的扩散范围存入数组,WA + TLE双重错误。
其次要同步男女朋友的时间,就是按时间去搜,只拓展这一秒能走到的范围。
最后枚举时间,只要能够遇见那么有一个格子他们都走过,就能结束搜索了。这道题目前看来还不是很清楚,也没想明白为什么我采用visited[x][y] == 1 和 visited[x][y] == 2这样判断就答案错误,采用三维visited标记才能对,理解还不够,加油~~~~
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 805;
struct info{
int x,y;
int step;
}s[2],b,g;
int n,m;
int dx[] = {1,-1,0,0};
int dy[] = {0,0,-1,1};
char map[maxn][maxn];
int visited[2][maxn][maxn];
queue<info>q[2];
bool check(int x,int y) //障碍判断
{
if(x < 0 || x >= n || y < 0 || y >= m
|| map[x][y] == 'X' || map[x][y] == 'Z'){
return true;
}
else{
return false;
}
}
int dist(int x,int y,int t) //曼哈顿距离估计
{ //当前点的距离离鬼的距离是不是鬼扩散的2倍距离内
for(int i = 0;i < 2;i++){
if(abs(x - s[i].x) + abs(y - s[i].y) <= 2*t){
return true;
}
}
return false;
}
void clear_set() //初始化,清空队列
{
memset(visited,false,sizeof(visited));
while(!q[0].empty()){
q[0].pop();
}
while(!q[1].empty()){
q[1].pop();
}
}
int bfs(int k,int t)
{
info ptr,p;
int size = q[k].size();
while(size--){ //核心重点,防止新入队的点也进行更新
info ptr = q[k].front();
q[k].pop();
if(dist(ptr.x,ptr.y,t)) { //起点已经被鬼魂覆盖 /*细节*/
continue;
}
for(int i = 0;i < 4;i++){
p.x = ptr.x + dx[i];
p.y = ptr.y + dy[i];
if(check(p.x,p.y) || dist(p.x,p.y,t) || visited[k][p.x][p.y]){
continue;
}
if(visited[!k][p.x][p.y]){ //走到对方的格子。
return true;
}
visited[k][p.x][p.y] = true;
q[k].push(p);
}
}
return false;
}
int solve()
{
q[0].push(b);q[1].push(g);
visited[0][b.x][b.y] = true;visited[1][g.x][g.y] = true;
int t = 0;
while(!q[0].empty() || !q[1].empty()){ //枚举每一秒钟
t++;
for(int i = 1;i <= 3;i++){ //一秒走三步
if(bfs(0,t)){
return t;
}
}
if(bfs(1,t)){ //一秒走一步
return t;
}
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
clear_set();
scanf("%d%d",&n,&m);
int cnt = 0;
for(int i = 0;i < n;i++){
scanf("%s",map[i]);
for(int j = 0;j < m;j++){
if(map[i][j] == 'Z'){ //存储鬼的坐标
s[cnt].x = i;s[cnt++].y = j;
}
if(map[i][j] == 'M'){
b.x = i;b.y = j;
}
if(map[i][j] == 'G'){
g.x = i;g.y = j;
}
}
}
printf("%d\n",solve());
}
return 0;
}
不定时更新~~~