Colored Cubes(彩色立方体)题解
题意:有n个带颜色的立方体,每个面涂有一种颜色,要求重新涂尽量少的面,使的所有的立方体完全相同(两个立方体相同的含义是,存在一种旋转的方式,是的两个立方体对应面的颜色是相同的)
思路
首先定义一个数组p[i]表示编号i所在立方体中的位置,假设6个面的编号分别为1-6,其中位置和数字的对应关系如下,其实这就是一个标准的立方体的一个排列
1 2 3 4 5 6
前 右 上 下 左 后
根据编号1,2,3,4,5,6看他们所在的位置然后对应到相应的数字
标准姿态的p[i]为{1,2,3,4,5,6}//先假设下标从1开始
标准姿态向左边旋转{5,1,3,4,6,2};
标准姿态向上面旋转{3,2,6,1,5,4};
我们确定一下,一个立方体确定一个顶面和前后左右四个面选择一个正面,则这个立方体就唯一确定了。一共有6*4=24中姿态
我们发现只要确定了向左边和向上面的旋转方式,这24中姿态我们就可以全部用标准姿态向上旋转或者是向左旋转推出来
数组的下标从0开始,所以我们之前推出来的全部要减去1
我们将这24中情况打表出来。
小细节:
1:本题我们需要随时获取旋转后的姿态,而原来的p[N]在旋转的时候需要修改,会将原来的值覆盖,所以我们可以额外开一个数组,先将p数组的信息保存下来,这就要用到memcpy函数了。
2:打表的时候,外层循环是先确定顶层面是编号几,内层循环是正面一共有的4中情况。
3:本题的读入技巧,面对每一行既有字符串和空格还有一些重复的字符串,我们读入的时候还要去重,我们可以用scanf("%s",name)来读入,因为%s碰到空格和换行都是会暂停的。至于怎么去重,而且要返回第一次出现的下标,我们可以用unorder_set names;也可以自己手写一个vector names;如果这个字符串还没有出现过的话,我们就直接返回names里面元素的大小,如果出现过的话,我们就从一个一个寻找,找到相同的就返回他的下标
vector<string> names;
int Id(const char* name){
string s(name);
int n=names.size();
for(int i=0;i<n;i++)
if(s==names[i])return i;
names.push_back(s);
return n;
}
打表代码
#include<iostream>
#include<cstring>//memcpy需要的头文件
int left[6] = { 4,0,2,3,5,1 };
int up[6] = { 2,1,5,0,4,3 };
//将标准姿态p按照排列t旋转(将标准排列向左转或者是向上转)
void rot(int p[], int t[]) {
int q[6];
memcpy(q, p, sizeof(q));//从p的首地址开始去q个字节赋值给q
for (int i = 0; i < 6; i++)
p[i] = t[q[i]];
}
void dabiao() {
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(p));
if (i == 0) { rot(p, up); }//1在顶面相当于将标准姿态向上旋转,然后向左边旋转0-3次
if (i == 1) { rot(p, left); rot(p, up); }//2在顶面相当于将标准姿态先向左边转,在向上面转,然后向左边旋转0-3次,以下依次类推
//if(i==2){}标准姿态就在顶面,不用旋转
if (i == 3) { rot(p, up); rot(p, up); }
if (i == 4) { rot(p, left); rot(p, left); rot(p, left); rot(p, up); }
if(i==5) { rot(p, left); rot(p, left); rot(p, up); }
for (int j = 0; j < 4; j++) {
printf("{%d, %d, %d, %d, %d, %d},\n", p[0], p[1], p[2], p[3], p[4], p[5]);
//确定一次正面后,将姿态先走遍旋转0-3次
rot(p,left);
}
}
printf("}\n");
}
int main() {
dabiao();
return 0;
}
接下来就是如何实现这个问题了:
首先我们枚举每一个立方体的24个姿态,每一个立方体的旋转方式用r[d]来表示,从第二个立方体开始枚举,一直枚举到最后一个立方体,我们就要来算这个最小涂色的次数了。
我们先求出每一个立方体的六个面中哪一种颜色被涂的次数最多,然后和这个颜色不同的一律重新涂,最后将这个最小的次数数出来就行
AC代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
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},
};
int n;
const int N=5;
int dice[N][6], ans;
vector<string> names;
int Id(const char* name){
string s(name);
int n=names.size();
for(int i=0;i<n;i++)
if(s==names[i])return i;
names.push_back(s);
return n;
}
int r[N], color[N][6];//每个立方体的旋转方式,旋转后各个面的颜色
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];
}
}
int total=0;
for(int i=0;i<6;i++){
int count[24];
memset(count,0,sizeof(count));
int maxface=0;
for(int j=0;j<n;j++){//对于每一个立方体的六个面,枚举每一个面,分别选一个出现次数最多的颜色作为标准
maxface=max(maxface,++count[color[j][i]]);
}
total+=n-maxface;//和他颜色不同的话就一律重涂,就将颜色不同的个数计算出来
}
ans=min(ans,total);
}
void dfs(int d) {//从第二个立方体开始寻找一直到最后一个立方体,然后开始计算最少需要给多少个面涂色
if (d == n) check();
else
for (int i = 0; i < 24; i++) {//枚举每个立方体的姿态
r[d] = i;
dfs(d + 1);
}
}
int main() {
while (scanf("%d", &n) == 1 && n) {
names.clear();
for(int i=0;i<n;i++){
for (int j = 0; j < 6; j++) {
char name[30];
scanf("%s", name);
dice[i][j] = Id(name);
}
}
ans = n * 6;//所有面都涂一样的颜色
r[0] = 0;//第一个立方体不需要旋转
dfs(1);
printf("%d\n", ans);
}
return 0;
}