ZOJ 3814:模拟和状态压缩BFS

这题绝对是一个大坑……


题意是,有一个拼图有9块,每一块的四条边都可能有齿轮。每次你可以选择把一块顺时针转90度,但是当你转这个的时候,其相邻的图块如果和它相邻的边两边都有齿轮的话,就会咬合,使得旁边的图块也跟着往相反的方向转,同时这种影响是传递的,也就是说你转一个图块,可能9块都跟着动。


这道题实现起来,有非常多的优化细节要注意:

1. 千万不要直接去转8*8的那个字符矩阵,这样复杂度太高了。正确的方法是,由于每个图块只有4种状态,所以我们可以预先把初态和目标状态的每一块单独比较(不考虑咬合),记录下对应位置的图块转几次能转出目标位置的样子。于是你会发现,实际上状态只有4^9种,其实并不大,我们接下来就用0123表示每个格的状态,这样就大大地压缩了一步;

但是需要注意的是,目标状态可能有很多种,因为有可能一块图是对称的,比如样例中间那块,转0、1、2、3次都可以。所以这个都要记录下来。

2. 接下来你会发现这个事情用BFS就可以解决了,但是仔细考虑一下,状态复杂度4^9,转移复杂度9(选择)*9(操作),还是挺紧的……

3. 对于四边齿轮的处理,千万不要把它记录到你的格子结构体里去,因为这样当你转格子的时候还需要额外维护这个数组,增加了复杂度,正确的方法是单开一个数组,就记录初始的状态,并且将其用二进制表示,这样,你知道每个格子转了多少次,你就能通过几步位运算知道这个格子四周的齿轮是什么样子了。

4. 由于转一个格子可能联动转很多个格子,BFS里面还需要套一个DFS/BFS,用于判断每个格子应该怎么转。省时间的方法是这里开一个栈,记录都哪些格子需要转,这样能省些时间,不用每次都把所有9个格子都看一遍。

5. 另一个省时间的办法是,当你预处理完所有的终结状态以后,直接DFS一下,将所有的终止状态的数值记录下来,这样每次你比对的时候直接O(1)就完成了;

6. 还有一个细节是,每次从队列里取出一个整数状态后解码,之后真正要转的时候可以直接对整数进行位操作,方法就是取出那一位,改了之后再塞回去。这样就不用操作数组的那一位,之后再算hash值了。在这么大的基数下,一点不对可能就会TLE。


实现上也有些细节,关键就在于STL的queue。首先queue是没有清零函数的,要清零就只能一个一个pop,在这道题里慢得跟蜗牛一样,大BFS里要是用的话千万不要开在全局变量里,把BFS弄成一个函数,用一个申请一个。另外就是STL的queue申请是【很慢的】!所以小BFS里千万不要用queue,我T了两天就T在这里…… 所以我才算明白为什么大家都用DFS,之后我手写了一个队列瞬间就出来了。总之,一定避免频繁地申请queue,偶尔用用还行。


最后就是这个代码里凡是a[5][5]的地方,我原来开a[4][4],但是死活segmentation fault,我也查不出哪儿可能访问越界,之后改成5就能过了……我也不知道怎么搞的……


#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;

struct Panel
{
	char c[10][10];
	
	void spin(bool clockwise)
	{
		char tmp[10][10]={};
		if(clockwise)
		{
			int i,j;
			for(i=1; i<=8;i++)
			{
				for(j=1;j<=8;j++)
				{
					tmp[j][9-i] = c[i][j];
				}
			}
			
			memcpy(c,tmp,sizeof(tmp));
		}
		else
		{
			int i,j;
			for(i=1; i<=8; i++)
			{
				for(j=1; j<=8; j++)
				{
					tmp[j][i] = c[i][j];
				}
			}
			
			memcpy(c, tmp, sizeof(tmp));
		}
	}
	
	bool operator ==(const Panel &b) const
	{
		int i,j;
		for(i=1;i<=8;i++)
		{
			for(j=1;j<=8;j++)
			{
				if(c[i][j] != b.c[i][j])
					return false;
			}
		}
		return true;
	}
	bool operator !=(const Panel &b) const
	{
		int i,j;
		for(i=1;i<=8;i++)
		{
			for(j=1;j<=8;j++)
			{
				if(c[i][j] != b.c[i][j])
					return true;
			}
		}
		return false;
	}
};

struct State
{
	Panel panel[5][5];
};

int two[30]={0};

int cp[5][5];

int toNum(int a[5][5])
{
	int sum=0;
	int i,j;
	for(i=1;i<=3;i++)
	{
		for(j=1;j<=3;j++)
		{
			sum += two[2* ( (i-1)*3 + (j-1) )] * a[i][j];
		}
	}
	return sum;
}

State state;
State final;

const int dx[4]={1,0,-1,0};
const int dy[4]={0,1,0,-1};
const int trans[4] = {2,3,0,1};

int final_state[5][5][10];
int p_final_state[5][5];

bool state_mark[1000000];

pair<int, int> q[500000];
int head=0, tail=0;

int link[5][5];
int finish[1000000];

void make_finish(int now, int sum)
{
	if(now == 0)
	{
		finish[sum] = 1;
		return;
	}
	int i;
	int x = (now-1)/3+1;
	int y = (now-1)%3+1;
	for(i=0;i<p_final_state[x][y] ;i++)
	{
		make_finish(now-1, sum*4 + final_state[x][y][i]);
	}
	return;
}

void decode(int ret[5][5], int code)
{
	int i=1, j=1;
	while(code>0)
	{
		ret[i][j] = code%4;
		code/=4;
		j++;
		if(j==4)
		{
			j=1;
			i++;
		}
	}
	return;
}

void push(pair<int, int> p)
{
	q[tail++] = p;
	return;
}
pair<int, int> front()
{
	pair<int, int> ret = q[head];
	head++;
	return ret;
}

void input(State &now)
{
	int i,j;
	for(i=1; i<=24;i++)
	{
		for(j=1;j<=24;j++)
		{
			char tmp;
			scanf("%c", &tmp);
			if(tmp!='#' && tmp!='.')
			{
				j--;
				continue;
			}
			now.panel[(i-1)/8+1][(j-1)/8+1].c[(i-1)%8+1][(j-1)%8+1] = tmp;
		}
	}
	return;
}

int calc_link(int x, int y, int times) // 0111 1011 1101 1110 order = 3210
{
	int ret = link[x][y];
	
	int tmp = ret & (two[times]-1);
	ret = ret >> times;
	ret += tmp << (4 - times);

	return ret;
}

struct Q_node
{
	int x;
	int y;
	int color;
	Q_node(){}
	Q_node(int _x, int _y, int _color)
	{
		x=_x;
		y=_y;
		color=_color;
	}
};

Q_node stack[40];
int p_stack=0;

Q_node ds_q[40];
int ds_head=0, ds_tail=0;

void init()
{
	head=0;
	tail=0;
	
	memset(cp,0,sizeof(cp));
	memset(finish,0,sizeof(finish));
		
	memset(state_mark,0,sizeof(state_mark));
	memset(link, 0, sizeof(link));
	
	memset(final_state,0, sizeof(final_state));
	memset(p_final_state,0,sizeof(p_final_state));
	
	memset(stack, 0, sizeof(stack));
	p_stack = 0;
	
	ds_head=ds_tail=0;
	memset(link, 0, sizeof(link));
	return;
}

void determine_spin(int now_state[5][5], int nowi, int nowj)
{
	ds_head=0;
	ds_tail=0;
	int mark[5][5]={0};
	memset(stack, 0, sizeof(stack));
	p_stack = 0;

	Q_node start(nowi, nowj, 1);
	ds_q[ds_tail++] = start;
	mark[nowi][nowj] = 1;
	stack[++p_stack] = start;
	
	while(ds_tail != ds_head)
	{
		Q_node now = ds_q[ds_head++];
		
		int link_now = calc_link(now.x, now.y, now_state[now.x][now.y]);
		
		int i;
		for(i=0;i<=3;i++)
		{
			if ( (link_now & two[i]) == 0 )
				continue;
			
			int i_adj = now.x + dx[i];
			int j_adj = now.y + dy[i];
			
			int link_adj = calc_link(i_adj, j_adj, now_state[i_adj][j_adj]);
			
			if(mark[i_adj][j_adj] ==0 && (link_adj & two[ trans[i] ] )!=0  )
			{
				mark[i_adj][j_adj]=1;
				Q_node node(i_adj, j_adj, -now.color);
				stack[++p_stack] = node;
				ds_q[ds_tail++] = node;
			}
		}
	}
	return;
}


int t;

int main()
{
	two[0]=1;
	for(int tmp_i=1; tmp_i<=20; tmp_i++)
		two[tmp_i] = two[tmp_i-1] *2;
	
	scanf("%d", &t);
	int files;
	for(files=1; files<=t; files++)
	{
		init();
		
		input(state);
		input(final);
		
		int i,j;
		
		for(i=1;i<=9;i++)
		{
			for(j=0;j<=3;j++)
			{
				int tmp;
				scanf("%d", &tmp);
				link[(i-1)/3+1][(i-1)%3+1] = (link[(i-1)/3+1][(i-1)%3+1] << 1) + tmp;
			}
		}
		
		bool ok=true;
		for(i=1;i<=3;i++)
		{
			for(j=1;j<=3;j++)
			{
				for(int k=1; k<=4;k++)
				{
					state.panel[i][j].spin(true);
					if(state.panel[i][j] == final.panel[i][j])
					{
						final_state[i][j][ p_final_state[i][j]++ ] = k%4;
					}
				}
				if(p_final_state[i][j] == 0)
				{
					printf("-1\n");
					ok=false;
					break;
				}
			}
			if(!ok)
				break;
		}
		if(!ok)
			continue;
			
		make_finish(9,0);
		
		
		int first_num = toNum(cp);
		if(finish[ first_num ]==1)
		{
			printf("0\n");
			continue;
		}
		
		push( make_pair(first_num, 0) );
		state_mark[first_num] = true;
		bool found = false;
		
		while(head!=tail)
		{
			pair<int, int> now = front();
			int now_state[5][5]={};
			decode(now_state, now.first);
			
			int mover = now.first;
			
			for(i=1; i<=3; i++)
			{
				for(j=1; j<=3; j++)
				{
					determine_spin(now_state,i,j);
					
					int i_stack;
					for(i_stack =1; i_stack<=p_stack; i_stack++)
					{
						int i_spin = stack[i_stack].x, j_spin = stack[i_stack].y;
						int spin_color = stack[i_stack].color;
						
						int digit_num = (i_spin-1) * 3 + j_spin-1;
						int this_digit = (mover/two[2*digit_num])%4;
						mover -= this_digit * two[2*digit_num];
						this_digit = ( (this_digit + spin_color) %4+4)%4;
						mover += this_digit * two[2*digit_num];
					}	
					
					//push
					int next_state = mover;
					if(state_mark[next_state]!=true)
					{
						if(finish[next_state]==1)
						{
							printf("%d\n", now.second+1);
							found = true;
							break;
						}
						
						state_mark[next_state]=true;
						push( make_pair(next_state, now.second + 1) );
					}
					mover = now.first;
				}
				if(found)
					break;
			}
			if(found)
				break;
		}
		
		if(!found)
			printf("-1\n");
	}
	//system("pause");
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值