2020笔试编程题两则浅析
A.骰子分类
问题描述:
Alice有一堆骰子,每枚骰子都是尺寸一样的正六面体,六个面上分别刻有1到6不同的数字。Alice发现有些骰子和其它的不一样,定义如果两枚骰子的六个面的全部对应相等则认为是同一种。给出N枚骰子的上下左右前后面上的数字,请统计这N枚骰子中共有几种不同的骰子,并且每种分别有几颗(从大到小输出)。
输入有多行,第一行是骰子的数量N,接下来有N行,每行是六个1到6不同数字用一个空格隔开,分别表示一枚骰子的上下左右前后的数字。
输出有多行,第一行是这些骰子中的种类数T,接下来T行,由大到小地输出各类骰子分别有多少枚。
简析
化学上有一种有机物的构形,叫作“手性”,即如果一个碳原子连接四个不同的基团,它有两种排列形式,呈现镜像关系,就像人的两只手,两只手的组成形式完全一样,又不能通过平移将两手重合。

由于基团的连接完全一样,若要描述手性的这种特征,还指定旋转方向描述。化学上对于手性分子的描述暂且不表。此题我们根据骰子的“手性”,规定一种骰子的命名方式:总是使1在上顶面,1周围的四个面中最小号旋转到前面,那么剩余的四个面也就被确定了下来。若分别记上下,左右,前后为记号ud,lr,fb,可以将骰子编号ufldbr(这样骰子编号最大值是136542,最小值123456,当然只要u和f分别是最高和次高位即可,剩余4位任意排列)。根据骰子编号,当且仅当骰子编号完全一致时才认为两个骰子是同一类。
对骰子的操作只有水平方向旋转(u和d不动,将左面旋转到前面)和垂直方向旋转(l和r不动,将下面旋转到前面)。通过输入数据每得到一个骰子,就先通过旋转的方式将骰子变成1为顶面,周围四面最小号为前面。这样将它用一个hashTable(比如一个int数组)保存各个种类的骰子,为hashTable排序并输出即可。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using std::sort;
using std::min;
struct theDice
{
int u,d,l,r,f,b;
// up, down
// left,right
// front,back
theDice() {};
theDice(int uu,int dd,
int ll,int rr,
int ff,int bb)
{
u=uu;
d=dd;
l=ll;
r=rr;
f=ff;
b=bb;
}
int hash()
{
return ((((10*u + f)*10 + l)*10 + d)*10+ b)*10 + r;
}
};
theDice vRotate(theDice a)
{
return theDice(a.f,a.b,a.l,a.r,a.d,a.u);
}
theDice hRotate(theDice a)
{
return theDice(a.u,a.d,a.b,a.f,a.l,a.r);
}
theDice normlize(theDice a)
{
theDice ret;
// move `1` to upward
if(a.u==1)
{
ret =a ;
}
else if (a.d==1)
{
ret = vRotate(vRotate(a));
}
else if(a.f==1)
{
ret = vRotate(a);
}
else if(a.b==1)
{
ret = vRotate(hRotate(hRotate(a)));
}
else if (a.l ==1)
{
ret = vRotate(hRotate(a));
}
else if(a.r==1)
{
ret = vRotate(hRotate(hRotate(hRotate(a))));
}
//here, ret is always upward == 1
//next, move the min to front
int frontVal = min(ret.l,min(ret.r,min(ret.f,ret.b)));
if(ret.l==frontVal)
{
ret = hRotate(ret);
}
else if(ret.b==frontVal)
{
ret = hRotate(hRotate(ret));
}
else if(ret.r==frontVal)
{
ret = hRotate(hRotate(hRotate(ret)));
}
// else a.f == frontVal, do nothing
return ret;
}
#define MAXN 16000
// in fact, MAXN only need 136542 - 123456 = 13086
int main(void)
{
int i,N;
int u,d,l,r,f,b;
theDice *dice;
scanf("%d",&N);
dice = new theDice[N];
int typeCounter=0;
int bucket[MAXN];
memset(bucket,0,sizeof(bucket));
for(i=0;i<N;i++)
{
scanf("%d%d%d%d%d%d",&u,&d,&l,&r,&f,&b);
dice[i] = normlize(theDice(u,d,l,r,f,b));
int t=dice[i].hash() - 123456;
if(bucket[t]==0)
{
typeCounter +=1;
}
bucket[t]+=1;
}
sort(bucket,bucket+MAXN);
printf("%d\n",typeCounter);
for(i=MAXN-1;i>=0;i--)
{
if(bucket[i]==0)
{
break;
}
printf("%d\n",bucket[i]);
}
delete [] dice;
return 0;
}B.美食家
问题描述
Alice的公司提供早、午、晚餐,但是996的Alice加班太晚而早晨难以按时起床,就形成了不吃早餐的习惯。这样Alice要在公司的午餐和晚餐中选择。公司里的午餐有N份,晚餐有M份,每份含有\(x\)的热量和\(y\)的美味值,Alice希望在吃到美味值不低于\(T\)的情况下,摄入的热量最低。Alice需要在午餐和晚餐当中至多各选一份。如果只吃一餐就满足了美味值的要求,Alice也可以只吃这一餐。
编写程序计算Alice摄入的最低的热量值,如果根据菜谱无论如何Alice也吃不到美味值不低于T的两餐,则输出”-1″;
输入数据有多行,第一行有三个用空格分隔的正整数N,M,T分别表示午餐份数、晚餐份数、美味值下限,接下来有N+M行,前N行表示午餐,后M行表示晚餐,每行都是两个用空格分隔的正整数,分别表示热量和美味值。
输出只有1行,一个整数,表示美味值不小于T的情况下可能摄入热量的最小值。
简析
抽象成数学模型:有两组二维平面的点(包含原点,表示不吃这一餐),从每组向量中各选出一个点,使这两点在 \(x_i+x_j \geq T\) 的条件下使 \( y_i+y_j \) 最小。
将这两个点如果视为两个向量,就是两个向量之和 \((x,y) \),在横坐标\(x\)大于T的时候使\(y\)尽量小,所以应当先选择那些斜率比较低的,即向量的\( y/x \)尽量小。

建立两个列表分别保存午餐和晚餐,输入后根据\( \frac{热量}{美味值} \)排序,为了防止除0,只吃1餐的情况单独使用1重循环做判断,然后是二重循环计算午餐+晚餐的情况。
虽然是二重循环,但是平均时间是排序的\( O(N \cdot \log{N}) \),平凡情况下这个二重循环大概是\( O(\min\{N,M\}) \)的,因为我们只需要找到首个\( y/x \)最小并且 \(x_i+x_j \geq T\)的向量就能够退出循环。
不过,从数学上严格来讲,这种方法仍存在缺陷。如果题目的数据比较弱,这种方法是能得分的。
参考代码
#include <cstdio>
#include <algorithm>
#define INF 0x7FFFFFF
using std::sort;
int N,M,T;
struct foodNode{
int heat,taste;
bool operator<(const foodNode &o)
{
return heat*1.0/taste < o.heat*1.0/o.heat; // assume tast never 0
}
};
int main(void)
{
int i,j;
scanf("%d%d%d",&N,&M,&T);
foodNode *lunch,*dinner;
lunch = new foodNode[N];
dinner = new foodNode[M];
for(i=0;i<N;i++)
{
scanf("%d%d", &(lunch[i].heat),&(lunch[i].taste));
}
sort(lunch,lunch+N);
for(i=0;i<M;i++)
{
scanf("%d%d", &(dinner[i].heat),&(dinner[i].taste));
}
sort(dinner,dinner+M);
// only eat one meal
int minHeat=INF;
for(i=0;i<N;i++)
{
if(lunch[i].taste>=T)
{
minHeat = std::min(minHeat,lunch[i].heat);
break;
}
}
for(i=0;i<M;i++)
{
if(dinner[i].taste>=T)
{
minHeat = std::min(minHeat,dinner[i].heat);
break;
}
}
int target;
char winFlag = 0;
// lunch + dinner
for(i=0;i<N && winFlag ==0 ;i++)
{
target = T-lunch[N].taste;
for(j=0;j<M && winFlag ==0;j++)
{
if(dinner[j].taste>=target)
{
minHeat = std::min(minHeat,target+dinner[j].heat);
winFlag = 1;
break;
}
}
}
delete [] lunch;
delete [] dinner;
if(minHeat == INF)
{
printf("-1\n");
}
else
{
printf("%d\n",minHeat);
}
return 0;
}