例题1.8 彩色立方体 Colored Cubes UVALive - 3401 暴力打表+暴力搜索+贪心

传送门

题目大意:有n个带颜色的立方体,每个面都涂有不同的颜色,要求图改尽量少的面,使得所有立方体完全相同,立方体可以旋转。

解题思路:如果立方体每个面都不一样,那么立方体通过旋转一共有4×6=24中状态。我们给每个面标号0,1,2,3,4,5  那么旋转可以生成24中序列。这个序列手写很麻烦,可以用程序生成。然后将这二十四个状态当作常量表,直接使用即可。

 第一个立方体作为参照,然后就是枚举每个立方体的状态,用递归实现即可,递归到最后一个立方体时,计算每个面需要涂改的面,得出每种状态需要涂改的面。找出所有情况的最小值即可。


生成常量表的程序代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int main()
{
	int A[] = {0,1,2,3,4,5};
	for(int i=0; i<4;i++) //上前下后四个面分别当顶面
	{
		for(int j = 0; j<4; j++)
		{
			int m = A[0];
			A[0] = A[1];
			A[1] = A[5];
			A[5] = A[4];
			A[4] = m;
			printf("{%d, %d, %d, %d, %d, %d},", A[0], A[1], A[2], A[3], A[4], A[5]);
		}
		int x = A[2];
		A[2] = A[0];
		A[0] = A[3];
		A[3] = A[5];
		A[5] = x;
	}
	int x = A[2]; //右面当顶面
	A[2] = A[1];
	A[1] = A[3];
	A[3] = A[4];
	A[4] = x;
	for(int j = 0; j<4; j++)
	{
		int m = A[0];
		A[0] = A[1];
		A[1] = A[5];
		A[5] = A[4];
		A[4] = m;
		printf("{%d, %d, %d, %d, %d, %d},", A[0], A[1], A[2], A[3], A[4], A[5]);
	}
	x = A[2];//左面当顶面
	A[2] = A[1];
	A[1] = A[3];
	A[3] = A[4];
	A[4] = x;
	x = A[2];
	A[2] = A[1];
	A[1] = A[3];
	A[3] = A[4];
	A[4] = x;
	for(int j = 0; j<4; j++)
	{
		int m = A[0];
		A[0] = A[1];
		A[1] = A[5];
		A[5] = A[4];
		A[4] = m;
		printf("{%d, %d, %d, %d, %d, %d},", A[0], A[1], A[2], A[3], A[4], A[5]);
	}
	return 0;
}



AC代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <stack>
#include <vector>
#include <string>

#define mem(s) memset(s, 0, sizeof(s))

using namespace std;

int n, pos[25], dice[4][6], ans = 1 << 31 -1;
int Vist[24][6] = { {1, 5, 2, 3, 0, 4},{5, 4, 2, 3, 1, 0},{4, 0, 2, 3, 5, 1},{0, 1, 2, 3, 4, 5},{1, 2, 0, 5, 3, 4},{2, 4, 0, 5, 1, 3},{4, 3, 0, 5, 2, 1},{3, 1, 0, 5, 4, 2},{1, 0, 3, 2, 5, 4},{0, 4, 3, 2, 1, 5},{4, 5, 3, 2, 0, 1},{5, 1, 3, 2, 4, 0},{1, 3, 5, 0, 2, 4},{3, 4, 5, 0, 1, 2},{4, 2, 5, 0, 3, 1},{2, 1, 5, 0, 4, 3},{3, 5, 1, 4, 0, 2},{5, 2, 1, 4, 3, 0},{2, 0, 1, 4, 5, 3},{0, 3, 1, 4, 2, 5},{2, 5, 4, 1, 0, 3},{5, 3, 4, 1, 2, 0},{3, 0, 4, 1, 5, 2},{0, 2, 4, 1, 3, 5} 
	};

int Color[25];

vector<string> names;
int ID(const char * name)  //给每种颜色一个ID
{
	string s(name); //把name赋值给s, 等同于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 turn(int x)
{
	if(x == n) //找到最后一个立方体了。
	{
		int sum = 0;
		for(int i=0; i<6; i++)
		{
			int max_color = 0;
			mem(Color);
			for(int j = 0; j<n; j++)
			{
				//这一句可能比较难想, 详细解释一下:Color保存的时所有颜色在这个面(所有立方体)出现的次数
				//dice保存的是原来立方体的每个面的颜色
				//Vist保存的是旋转后 0,1,2,3,4,5,面对应原来哪个面的颜色
				//pos保存搜索的哪个姿态。
				//综合就是pos姿态下找到当前颜色对应的原来的哪个面,然后得出哪个颜色,让这个颜色+1. 实在想不通可以看下面蓝书的代码,这一步上我省了一个步骤,那个要好理解,道理一样
				max_color = max(max_color, ++Color[ dice[j][ Vist[pos[j] ][i] ] ]);
			}
			sum += n - max_color;
		}
		ans = min(ans, sum);
	}
	else for(int i=0; i<24; i++)
	{
		pos[x] = i;
		turn(x+1);//搜索下一个立方体
	}
}

int main()
{
	char name[30];
	while(scanf("%d", &n) && n)
	{
		ans = 1 << 31 -1;
		names.clear();
		for(int i=0; i<n; i++)
			for(int j =0; j<6; j++)
			{
				scanf("%s", name);
				dice[i][j] = ID(name);  //记录每个立方体每个面的颜色
			}
		turn(0);
		printf("%d\n", ans);
	}
	return 0;
}


蓝书代码:

打表:

// LA3401/UVa1352 Colored Cubes: table maker
// Rujia Liu

#include<cstdio>
#include<cstring>

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

// ╟╢ууееапTпЩв╙вкл╛p
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]];
}

void enumerate_permutations() {
  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);
    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++) {
      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() {
  enumerate_permutations();
  return 0;
}

主程序:

// LA3401/UVa1352 Colored Cubes
// Rujia Liu

// generated by la3401_make.cpp, then removed extra spaces
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},
};

#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn = 4;
int n, dice[maxn][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(names[i] == s) return i;
  names.push_back(s);
  return n;
}

int r[maxn], color[maxn][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 tot = 0; // 需要重新涂色的面数
  for(int j = 0; j < 6; j++) { // 考虑每个面
    int cnt[maxn*6]; // 每种颜色出现的次数
    memset(cnt, 0, sizeof(cnt));
    int maxface = 0;
    for(int i = 0; i < n; i++)
      maxface = max(maxface, ++cnt[color[i][j]]);
    tot += n - maxface;
  }
  ans = min(ans, tot);
}

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;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值