题目概述:
有n个带颜色的立方体,每个面都涂有一种颜色。要求重新涂尽量少的面,使得所有立方体完全相同。两个立方体相同的含义是:存在一种旋转方式,是的两个立方体对应面的颜色相同。
思路:
每一个立方体有6个面,记为0,1,2,3,4,5.然后0面在上时,再讨论四个面哪个在前的情况。一共有6个面可以在上,每一个里面有四个面在前的情况。所以每个立方体都有24种姿态。定一动多。把一个立方体定住不动,作为参考系,然后再枚举剩下的立方体的姿态。在每一种确定的姿态里,比较每个面的颜色。以最多的颜色为准色,不一样的全都涂成准色。然后每一种确定的姿态都会算出一个结果,再取最小值即可。
注:24种姿态可事先通过程序枚举出来。注意!题目中每个面的编号是固定的!不可修改。
枚举姿态代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
void trans(int *std,int *p) //std数组是用作变换的。p数组是现在的面的情况。
{
int q[6];
for (int i=0;i<6;i++) q[i]=p[i]; //因为p会改变,所以先把p的值赋给一个不动的数组q,代为p用。
for (int i=0;i<6;i++)
{
p[i]=q[std[i]]; //现在第i个面(p[i])的位置应该是变换前状态第std[i]面的编号。【注意理解!】
}
}
int main()
{
int up[6]={3,1,0,5,4,2};
int left[6]={1,5,2,3,0,4}; //left和up数组实际上是关系数组。画画图可知,初始状态为0,1,2,3,4,5.上翻(up)后,第0面的位置变成了刚刚的3面,第1面的位置不变,第2面的位置变成了刚刚的0面……以此类推。实际是表示的面和面的关系。
int stds[6]={0,1,2,3,4,5};
for (int k=0;k<6;k++)
{
int s[6];
for (int i=0;i<6;i++) s[i]=stds[i];
if (k == 1)
{
trans(up,s);
}
if (k == 2)
{
trans(left,s);
trans(up,s);
}
if (k == 3)
{
trans(left,s); trans(left,s); trans(up,s);
}
if (k == 4)
{
trans(left,s); trans(left,s); trans(left,s); trans(up,s);
}
if (k == 5)
{
trans(up,s); trans(up,s);
}
for (int i=0;i<4;i++)
{
printf("{%d, %d, %d, %d, %d ,%d}",s[0],s[1],s[2],s[3],s[4],s[5]);
trans(left,s);
}
cout<<endl;
}
return 0;
}
运行结果为:
{0, 1, 2, 3, 4 ,5},{1, 5, 2, 3, 0 ,4},{5, 4, 2, 3, 1 ,0},{4, 0, 2, 3, 5 ,1},
{3, 1, 0, 5, 4 ,2},{1, 2, 0, 5, 3 ,4},{2, 4, 0, 5, 1 ,3},{4, 3, 0, 5, 2 ,1},
{3, 5, 1, 4, 0 ,2},{5, 2, 1, 4, 3 ,0},{2, 0, 1, 4, 5 ,3},{0, 3, 1, 4, 2 ,5},
{3, 4, 5, 0, 1 ,2},{4, 2, 5, 0, 3 ,1},{2, 1, 5, 0, 4 ,3},{1, 3, 5, 0, 2 ,4},
{3, 0, 4, 1, 5 ,2},{0, 2, 4, 1, 3 ,5},{2, 5, 4, 1, 0 ,3},{5, 3, 4, 1, 2 ,0},
{5, 1, 3, 2, 4 ,0},{1, 0, 3, 2, 5 ,4},{0, 4, 3, 2, 1 ,5},{4, 5, 3, 2, 0 ,1}
然后提交程序代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
int meiju[24][6]={ //枚举姿态数组
{0, 1, 2, 3, 4 ,5},{1, 5, 2, 3, 0 ,4},{5, 4, 2, 3, 1 ,0},{4, 0, 2, 3, 5 ,1},
{3, 1, 0, 5, 4 ,2},{1, 2, 0, 5, 3 ,4},{2, 4, 0, 5, 1 ,3},{4, 3, 0, 5, 2 ,1},
{3, 5, 1, 4, 0 ,2},{5, 2, 1, 4, 3 ,0},{2, 0, 1, 4, 5 ,3},{0, 3, 1, 4, 2 ,5},
{3, 4, 5, 0, 1 ,2},{4, 2, 5, 0, 3 ,1},{2, 1, 5, 0, 4 ,3},{1, 3, 5, 0, 2 ,4},
{3, 0, 4, 1, 5 ,2},{0, 2, 4, 1, 3 ,5},{2, 5, 4, 1, 0 ,3},{5, 3, 4, 1, 2 ,0},
{5, 1, 3, 2, 4 ,0},{1, 0, 3, 2, 5 ,4},{0, 4, 3, 2, 1 ,5},{4, 5, 3, 2, 0 ,1}
};
int n,ans;
int precolor[4][6];
int color[4][6];
int r[4];
vector<string> names;
int ID(const char *name) //字符串转换数字功能
{
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;
}
void check() //运行到这时候,表示n个立方体的姿态已经确定。调用check函数检查需要涂色的最少数量。
{
for (int i=0;i<n;i++)
for (int j=0;j<6;j++)
color[i][meiju[r[i]][j]]=precolor[i][j]; //color数组第i个立方体在第r[i]种姿态下第j个面的颜色。【注意理解!】
int tot=0;
for (int j=0;j<6;j++)
{
int color_appear_num[24]; //记录第i种颜色出现的次数
memset(color_appear_num,0,sizeof(color_appear_num));
int max_color_num=0; //记录颜色出现的最多次数
for (int i=0;i<n;i++)
{
max_color_num=max(max_color_num,++color_appear_num[color[i][j]]);
}
tot=tot+n-max_color_num; //tot加上这一个面的涂色次数。涂色次数等于面数减去最多颜色出现次数。
}
ans=min(ans,tot); //答案取最小值。
}
void dfs(int x) //x表示正在确定第x个立方体的姿态。
{
if (x == n) check(); //x==n表示n个立方体的姿态已经确定。
else
for (int i=0;i<24;i++) //枚举第x个立方体的姿态。
{
r[x]=i;
dfs(x+1);
}
}
int main()
{
while (scanf("%d",&n) == 1 && n)
{
names.clear(); //使用vector实现字符串表示的颜色名到以数字代为表示的过程。实际上就是给颜色编号。
for (int i=0;i<n;i++)
for (int j=0;j<6;j++)
{
char name[30];
scanf("%s",name);
precolor[i][j]=ID(name); //precolor记录初始颜色。
}
ans=n*6; //颜色最多有n*6种,所以涂色的上界就是n*6
r[0]=0; //r数组用来记录姿态。i表示第i个立方体。r[i]表示姿态。0表示不动。以第一个立方体为参考系,所以r[0]=0。
dfs(1); //使用深搜来实现枚举。
cout<<ans<<endl;
}
return 0;
}