【数据结构•hash表】烦恼的设计师(SGOI)

题目描述

  烦恼的设计师(来自SGOI)
  春天到了,百花齐放,西湖公园里新设置了许多花坛,设计师想用不同的花摆出不同的图案以吸引游人,于是设计了各种图案并且在花圃中选好了要摆放的花。不幸的是负责搬运和摆放的工人因为临时有事,只将花放到花架上就匆匆离开了,并没有按照设计师原来的设计方案摆放,结果花坛杂乱不堪,设计师只好自己来调整花的位置。由于设计师通常从事脑力劳动,较少从事搬运和摆放花盆的体力工作,所以请你帮忙找出一种移动方法使工作量最小。
  不同种类的花有不同的类型编号,虽然地球上花的种类很多,但因为公园里的花不超过1,000,000种,所以花的类型编号不超过1,000,000。另一方面,出于美学考虑,一个花坛里摆放的不同种类的花不超过3种,且不同种类的花的数量不可太接近,对于任意两种花,数量多的花的盆数至少是数量少的花的2倍。
  花坛是正六边形的,共摆放有19盆花,每盆花都放在一个转盘上,转动一盆花下面的转盘,会使周围的6盆花顺时针或逆时针移动一个位置(但不可把花转到花坛外),称为一次操作。你的任务:用最少的操作使花坛由初始状态转化为符合设计图纸的目标状态。例如:
 

1556227523640086530.bmp

  
 

初始状态              目标状态

  如图,只需将处于圆心位置的那盆花的转盘顺时针转动一个位置,红色的花就移动到了目标位置。

输入输出格式

输入格式:

  输入文件共11行,1至5行描述花坛的初始状态,7至11行表示花盆应摆放的位置。中间以空行分隔,5行数字分别表示花坛的5个行,其中第1、5两行有3个整数,第2、4两行有4个整数,第3行有5个整数,表示每一行的花的类型,不同的数代表不同种类的花。

输出格式:

  输出文件,一行,包含一个整数,即最少的操作数,数据保证20步之内有解。

输入输出样例

输入样例#1:

1 1 1
1 2 1 1
1 1 1 1 1
1 1 1 1
1 1 1

1 1 1
1 1 1 1
1 1 2 1 1
1 1 1 1
1 1 1

输出样例#1:

1

提示信息

题意描述:
  给定两个正6边形的花坛,要求求出从第一个变化到第二个的最小操作次数以及操作方式。一次操作是:选定不在边上的一盆花,将这盆花周围的6盆花按照顺时针或者逆时针的顺序依次移动一个单位。限定一个花坛里摆放的不同种类的花不超过3种,对于任意两种花,数量多的花的盆数至少是数量少的花的2倍 。(这是 SGOI-8 的一道题)

解题分析:
  首先确定本题可以用广度优先搜索处理,然后来看问题的规模。正6边形共有19个格子可以用来放花,而且根据最后一句限定条件,至多只能存在 C(2,19) * C(5,17) = 1058148 种状态,用搜索完全可行。然而操作的时候,可以预料产生的重复节点是相当多的,需要迅速判重才能在限定时间内出解,因此想到了哈希表。那么这个哈希函数如何设计呢?注意到19个格子组成6边形是有顺序的,而且每一个格子只有3种可能情况,那么用3进制19位数最大 3^20-1=3486784400,注意,这个数值大于2^31,但小于2^32。于是我们将每一个状态与一个整数对应起来,使用除余法就可以了。

 算法使用:

哈希表,裸BFS(言简意赅!!!)

代码结构:

1.读入:

据题目中“花的类型编号不超过1,000,000。另一方面,出于美学考虑,一个花坛里摆放的不同种类的花不超过3种”可知,能以3进制数(long long)对每种可能的状态存储(类似离散化)

long long st=0,cnt=0,ed=0;//st为起始状态对应值,ed为目标状态,cnt为对应编号
for(int i=1;i<=19;i++)
{
	int x;
	cin>>x;
	if(mm[x]==0)//未出现?
	{
		m[x]=cnt++;
		mm[x]=1;
    } 
	st*=3;
	st+=m[x];
}
for(int i=1;i<=19;i++)
{
	int x;
	cin>>x;
	ed*=3;
	ed+=m[x];
}

2.广搜:

    q.push(P(st,0));//q为队列,P是pair<int,int>宏定义后
	while(!q.empty())//队列未空
	{
		P tmp=q.front();
		q.pop();
		if(tmp.first==ed)
		{
			cout<<tmp.second;
			return 0;
		}
		int th_tmp=tmp.first;
		for(int i=5;i>=1;i--)
		{
			for(int j=(i==5||i==1?3:(i==4||i==2?4:5));j>=1;j--)
			{
				a[i][j]=th_tmp%3;
				th_tmp/=3;
			}
		}//将数值状态转成数组
		for(int i=2;i<=4;i++)
		{
			for(int j=2;j<=(i==2||i==4?3:4);j++)
			{
				long long val1=round(i,j,1);//顺时针旋转(x,y,方向)
				if(find(val1)==0) //哈希寻找
				{
                    if(val1==ed) 
					{
						cout<<tmp.second+1;
						return 0;
					}//重点!!!!!帮助减少冗余扩展(不然就会超时——时间卡得太严)
					q.push(P(val1,tmp.second+1));//扩展
					gethash(val1);
				}
				long long val2=round(i,j,-1);//逆时针
				if(find(val2)==0) 
				{
                    if(val2==ed)
                    {
						cout<<tmp.second+1;
						return 0;
					}
					q.push(P(val2,tmp.second+1));
					gethash(val2);
				}
			}
		}
	}

3.哈希:

int val[siz];
inline int f(long long x) 
{
	return x%siz;//取余法
}
inline void gethash(int x) 
{
	int number=(f(x)+siz)%siz;
	while(val[number]!=x&&val[number]) number=(number==siz?0:number+1);//寻找没有冲突的位置
	val[number]=x;//记录
}
inline int find(int x)
{
	int number=(f(x)+siz)%siz;
	while(val[number]!=x&&val[number]) number=(number==siz?0:number+1);
	return val[number]==x;
}

4.旋转:

inline long long round(int x,int y,int f) {
	int b[7][7];
	for(int i=1; i<=5; i++)for(int j=1; j<=(i==5||i==1?3:(i==4||i==2?4:5)); j++)b[i][j]=a[i][j];
	if(x==3) {//第三行
		if(f==1) 
        {
			swap(b[x-1][y],b[x-1][y-1]);
			swap(b[x-1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x][y+1]);
		} else {
			swap(b[x-1][y],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x-1][y-1]);
		}
	} else if(x==2) {第二行
		if(f==1) {
			swap(b[x-1][y],b[x-1][y-1]);
			swap(b[x-1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y+1]);
			swap(b[x+1][y+1],b[x][y+1]);
		} else {
			swap(b[x-1][y],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y+1]);
			swap(b[x+1][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x][y-1]);
			swap(b[x][y-1],b[x-1][y-1]);
		}
	} else if(x==4) {第四行
		if(f==1) {
			swap(b[x-1][y],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x][y+1]);
			swap(b[x][y+1],b[x-1][y+1]);
		} else {
			swap(b[x-1][y],b[x-1][y+1]);
			swap(b[x-1][y+1],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x][y-1]);
		}
	}
	long long ans=0;
	for(int i=1; i<=5; i++) for(int j=1; j<=(i==5||i==1?3:(i==4||i==2?4:5)); j++)ans*=3,ans+=b[i][j];
	return ans;
}

5.时间复杂度:

状态数约为700000

O(700000*19+700000*2*7*2*19)\approxO(70000000)

是算法不加任何优化下的最优值——双端队列太麻烦,A*难写

在加上O(1),O(2)的情况下可以980毫秒通过:

6.完整代码:

#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
using namespace std;
typedef pair<int,long long> P;
int step[1000000];
int a[7][7],ans;
map<int,int> m;
map<int,bool> mm;
queue<P> q;
const int siz=1e7+7;
int val[siz];
inline int f(long long x) {
	return x%siz;
}
inline void gethash(int x) {
	int number=(f(x)+siz)%siz;
	while(val[number]!=x&&val[number]) {
		ans++;
		number=(number==siz?0:number+1);
	}
	val[number]=x;
}
inline int find(int x) {
	int number=(f(x)+siz)%siz;
	while(val[number]!=x&&val[number]) {
		ans++;
		number=(number==siz?0:number+1);
	}
	return val[number]==x;
}
inline long long round(int x,int y,int f) {
	int b[7][7];
	for(int i=1; i<=5; i++)for(int j=1; j<=(i==5||i==1?3:(i==4||i==2?4:5)); j++)b[i][j]=a[i][j];
	if(x==3) {
		if(f==1) {
			swap(b[x-1][y],b[x-1][y-1]);
			swap(b[x-1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x][y+1]);
		} else {
			swap(b[x-1][y],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x-1][y-1]);
		}
	} else if(x==2) {
		if(f==1) {
			swap(b[x-1][y],b[x-1][y-1]);
			swap(b[x-1][y-1],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y+1]);
			swap(b[x+1][y+1],b[x][y+1]);
		} else {
			swap(b[x-1][y],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y+1]);
			swap(b[x+1][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x][y-1]);
			swap(b[x][y-1],b[x-1][y-1]);
		}
	} else if(x==4) {
		if(f==1) {
			swap(b[x-1][y],b[x][y-1]);
			swap(b[x][y-1],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x+1][y]);
			swap(b[x+1][y],b[x][y+1]);
			swap(b[x][y+1],b[x-1][y+1]);
		} else {
			swap(b[x-1][y],b[x-1][y+1]);
			swap(b[x-1][y+1],b[x][y+1]);
			swap(b[x][y+1],b[x+1][y]);
			swap(b[x+1][y],b[x+1][y-1]);
			swap(b[x+1][y-1],b[x][y-1]);
		}
	}
	long long ans=0;
	for(int i=1; i<=5; i++) for(int j=1; j<=(i==5||i==1?3:(i==4||i==2?4:5)); j++)ans*=3,ans+=b[i][j];
	return ans;
}
int main() {
	long long st=0,cnt=0,ed=0;
	for(int i=1; i<=19; i++) {
		int x;
		cin>>x;
		if(mm[x]==0) {
			m[x]=cnt++;
			mm[x]=1;
		}
		st*=3;
		st+=m[x];
	}
	for(int i=1; i<=19; i++) {
		int x;
		cin>>x;
		ed*=3;
		ed+=m[x];
	}
	q.push(P(st,0));
	while(!q.empty()) {
		P tmp=q.front();
		q.pop();
		if(tmp.first==ed) {
			cout<<tmp.second;
			return 0;
		}
		int th_tmp=tmp.first;
		for(int i=5; i>=1; i--) {
			for(int j=(i==5||i==1?3:(i==4||i==2?4:5)); j>=1; j--) {
				a[i][j]=th_tmp%3;
				th_tmp/=3;
			}
		}
		for(int i=2; i<=4; i++) {
			for(int j=2; j<=(i==2||i==4?3:4); j++) {
				long long val1=round(i,j,1);
				if(find(val1)==0) {
					if(val1==ed) 
					{
						cout<<tmp.second+1;
						return 0;
					}
					q.push(P(val1,tmp.second+1));
					gethash(val1);
				}
				long long val2=round(i,j,-1);
				if(find(val2)==0) {
					if(val2==ed){
						cout<<tmp.second+1;
						return 0;
					}
					q.push(P(val2,tmp.second+1));
					gethash(val2);
				}
			}
		}
	}
	return 0;
}

另:实用小功能:ctrl+shift+A->压行

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值