例题1.8 彩色立方体 UVa1352

1.题目描述:点击打开链接

2.解题思路:本题要求重涂尽量少的一些面,使得n个立方体完全相同。看上去很棘手的一个问题。第一种想到的方法就是暴力搜索。但首先要弄明白的一个问题是:一个正方体有几种不同的旋转方式?如果我们选择一个面作为顶面,有6种选法;接下来选一个面作为前面,有4种选法,此时该正方体的“姿态”就确定了,根据乘法原理,一共有6*4=24种姿态(这里用姿态代指旋转方式,一种姿态就是一种旋转方式)。


好了,接下来该怎么生成这24种姿态呢?我们事先定义一种“标准姿态”:编号为i的面位于位置i的立方体(题目中每个面的编号是1~6,为了便于处理,在程序中编号范围改为0~5,标准姿态的放置方式就是题目中的样子)。那么其他的23种姿态都可以通过这种标准姿态旋转得到。比如将“标准姿态”以3-4为轴,向左旋转,那么1->5,5->6,6->2,2->1。如果我们令p[i]表示编号为i的面旋转后所在的位置,那么上述标准姿态左转后可以表示为{5,1,3,4,6,2}。由此,枚举姿态的思路就确定了:首先枚举某个编号为顶面,然后看它有几种姿态。比如:编号1在顶面时:先将标准姿态向上翻一次,在向左旋转0~3次;编号2为顶面时:将标准姿态向左旋转1次,向上翻1次,然后向左旋转0~3次;以此类推便能得到这24种姿态。


为了便于调试,这里有一个技巧:先独立写一个生成这24种姿态的程序,把这24种姿态输出到文件中,然后拷贝到最终的程序中当做常量表。这里把它们都放在数组dice24[24][6]中,dice24[i][j]便表示了第i种姿态编号j的位置。


接下来该暴力搜索了,那么如何“暴力”呢?这里的方法是先枚举每个立方体的姿态(第一个当做参考系,不用旋转),然后对于这6个面,分别选一个出现次数最多的颜色作为“标准”,和它不同的颜色一律重涂。这样,根据乘法原理易知,最多只有24^3种情况。本题的细节过多,这里只叙述了大致的枚举思路,详细细节见代码注释部分。

3.代码:

(生成24种姿态的代码)

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

int Left[] = { 4, 0, 2, 3, 5, 1 };
int up[] = { 2, 1, 5, 0, 4, 3 };

void rot(int*T, int*p)
{
	int q[6];
	memcpy(q, p, sizeof(q));
	for (int i = 0; i < 6; i++)p[i] = T[q[i]];//相当于映射:i->p->T
}
void enumerate_permutation()
{
	int p0[6] = { 0, 1, 2, 3, 4, 5 };
	printf("int dice24[24][6]={\n");
	for (int i = 0; i < 6; i++)
	{
		int p[6];
		memcpy(p, p0, sizeof(p0));//均从标准姿态开始
		if (i == 0)rot(up, p);//分别枚举将i变成顶面的操作
		if (i == 1){ rot(Left, p); rot(up, p); }
		if (i == 3){ rot(up, p); rot(up, p); }
		if (i == 4){ rot(Left, p); rot(Left, p); rot(Left, p); rot(up, p); }
		if (i == 5){ rot(Left, p); rot(Left, p); rot(up, p); }
		for (int j = 0; j < 4; j++)//侧面的4种情况
		{
			printf("{%d,%d,%d,%d,%d,%d},\n", p[0], p[1], p[2], p[3], p[4], p[5]);
			rot(Left, p);
		}
	}
	printf("};\n");
}
int main()
{
	freopen("t.txt", "w", stdout);
	enumerate_permutation();
	return 0;
}

(最终的程序)

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

int dice24[24][6] = {
	{ 2, 1, 5, 0, 4, 3 },
	{ 2, 0, 1, 4, 5, 3 },
	{ 2, 4, 0, 5, 1, 3 },
	{ 2, 5, 4, 1, 0, 3 },
	{ 4, 2, 5, 0, 3, 1 },
	{ 5, 2, 1, 4, 3, 0 },
	{ 1, 2, 0, 5, 3, 4 },
	{ 0, 2, 4, 1, 3, 5 },
	{ 0, 1, 2, 3, 4, 5 },
	{ 4, 0, 2, 3, 5, 1 },
	{ 5, 4, 2, 3, 1, 0 },
	{ 1, 5, 2, 3, 0, 4 },
	{ 5, 1, 3, 2, 4, 0 },
	{ 1, 0, 3, 2, 5, 4 },
	{ 0, 4, 3, 2, 1, 5 },
	{ 4, 5, 3, 2, 0, 1 },
	{ 1, 3, 5, 0, 2, 4 },
	{ 0, 3, 1, 4, 2, 5 },
	{ 4, 3, 0, 5, 2, 1 },
	{ 5, 3, 4, 1, 2, 0 },
	{ 3, 4, 5, 0, 1, 2 },
	{ 3, 5, 1, 4, 0, 2 },
	{ 3, 1, 0, 5, 4, 2 },
	{ 3, 0, 4, 1, 5, 2 },
};//24种姿态的常量表,dice24[i][j]表示第i种旋转姿态编号为j的面所在的位置(注意区分“编号”和“位置”)
#define N 4
int n, dice[N][6], ans;//dice[i][j]表示第i个立方体,编号为j的面的颜色
vector<string>names;
int ID(const char*name)//给每个颜色分配一个ID
{
	string s(name);
	int n = names.size();
	for (int i = 0; i < n; i++)
	if (names[i] == s)return i;
	names.push_back(s);
	return n;
}
int r[N], color[N][6];//r[i]表示第i个立方体的姿态编号,color[i][j]表示第i个立方体的位置为j的面的颜色(注意是“位置”为j,不是“编号”为j)
void check()
{
	for (int i = 0; i < n;i++)
	for (int j = 0; j < 6; j++)
		color[i][dice24[r[i]][j]] = dice[i][j];//编号为j的面在姿态i中对应的位置是dice24[i][j]
	int tot = 0;//统计需要重新涂的面的总次数
	for (int j = 0; j < 6; j++)//枚举每个面
	{
		int cnt[N * 6];//cnt[i]表示颜色i出现的次数
		memset(cnt, 0, sizeof(cnt));
		int maxface = 0;
		for (int i = 0; i < n; i++)//枚举每个立方体的位置为j的面的颜色
			maxface = max(maxface, ++cnt[color[i][j]]);//统计出现次数最多的颜色的次数
		tot += n - maxface;//在位置均为j时,需要重新涂的次数为n-maxface
	}
	ans = min(ans, tot);
}
void dfs(int d)//利用dfs枚举每个立方体的姿态,最多只有24^3种情况
{
	if (d == n)check();//枚举完毕时,开始检查有多少面需要重涂
	else for (int i = 0; i < 24; i++)
	{
		r[d] = i;
		dfs(d + 1);
	}
}
int main()
{
	//freopen("t.txt", "r", stdin);
	while (~scanf("%d", &n) && n)
	{
		names.clear();
		for (int i = 0; i < n;i++)
		for (int j = 0; j < 6; j++)
		{
			char name[30];
			cin >> name;
			dice[i][j] = ID(name);
		}
		ans = n * 6;
		r[0] = 0;
		dfs(1);
		printf("%d\n", ans);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值