[编程题]不想出差的HR
按照卡中心校园招聘的要求,HR小招和小商需要从三个科室中(分别为A、B、C)抽派面试官去往不同城市。
两名HR按照以下规定轮流从任一科室选择面试官:每次至少选择一位,至多选择该科室剩余面试官数。最先选不到面试官的HR需要自己出差。
假设HR小招和小商都不想出差且每次选择都采取最优策略,如果是小招先选,写一个函数来判断她是否需要出差。如果不需要出差,请给出第一步的最优策略。
输入描述:
输入为三个正整数,分别代表三个科室的面试官人数,用英文逗号分隔
输出描述:
若小招需要出差,则输出:1;
若小招不需要出差,则输出:第一步选择的科室名称和选择人数,用英文逗号分隔
输入例子1:
1,8,9
输出例子1:
1
输入例子2:
2,0,4
输出例子2:
C,2
分析:
这是Nim问题:
经典的Nim问题是:有若干堆石子,两个人轮流去拿某一堆石子中的一个或者多个,直到最后所有石子都被拿完,最后一个不能拿到石子的人失败。
定义两种情况
1当前先手必败
2当前先手必胜
以两堆石子为例,(3,3)的堆为例,(3,3)的子堆为(3,1),(3,2),(3,0)【石子之间的顺序不影响】,
1.(3,0)的所有可能为【(0,0)必败, (0,1)必胜, (0,2) 必胜 】;所以(3,0)肯定就先手必胜。
2. (3,1)的所有可能【(3,0)必胜,(2,1),(0,1)必胜,(1,1)】;
我们继续看(2,1)和(1,1),只要证明(2,1)or(1,1)是必败,(3,1)就一定是必胜。只要下一回合有一种可能先手必败,那么本回合一定是先手必胜,因为每个人都是选择最优策略让对手失败。
(2,1)可能的结果【(2,0)必胜,(1,1),(1,0)必胜】,继续看(1,1)所有可能的结果为【(1,0)】必胜,因为(1,1)下一回合所有结果都是先手必胜,那这一回合一定是先手必败,(0,1)先手必胜,那么(1,1)一定是先手必败。我们得出(2,1)下回合所有可能都是【(2,0)必胜,(1,1)必败,(1,0)必胜】,那么(2,1)必胜。再往前看(3,1)下回合所有可能【(3,0)必胜,(2,1)必胜,(0,1)必胜,(1,1)必败】,那么(3,1)必胜
3.(3,2)的所有可能【(3,1)必胜,(3,0)必胜,(2,2),(2,1)必胜】,(2,2)的所有可能【(2,1)必胜->【(2,0)必胜,(1,1)必败,(1,0)必胜】,(2,0)必胜】,(2,2)必败
所以(3,2)必胜
4.(3,3)的所有可能【(3,2)必胜,(3,1)必胜,(3,0)必胜】,(3,3)必败
我们可以通过推理本回合接下来的每个回合的输赢,来回溯判断本回合是必胜还是必输。
以上的性质可以通过a1 xor a2 xor a3 … xor ai 来表示。非常神奇
要证明结论是否正确,证明三个命题:
1.将所有不能移动到必胜局面的结果判断为必败局面
2.必胜局面一定可以移到必败局面
3.必败局面的所有下一个局面都是必胜局面
命题1:所有必败局面,a1 xor a2 xor a3 … xor ai xor an == 0
命题2:对于某个局面必胜 a1 xor a2 xor a3 … xor ai xor an!== 0,一定有个合法移动可以移动,使
a1 xor a2 xor a3 … xor ai =0,不妨设a1 xor a2 xor a3 … xor ai xor an == k,一定存在某个ai,他二进制最高位的1和k二进制最高位的1相同,ai xor k =ai’ , ai’小于ai,就好像从石子顿上拿掉了石子,使a1 xor a2 xor …ai’ xor an == 0必败一定成立
命题3:对于某个局面 a1 xor a2 xor a3 … xor ai==0,不存在一个合法移动,将ai变成ai’ 后,使
a1 xor a2 xor a3 … xor ai’ xor an == a1 xor a2 xor a3 … xor ai xor an , 因为ai=ai’,所以ai’不是一个合法的移动
public class Main {
public static int getHigh(int a) {
int c=0;
while(a!=0) {
a=a>>1;
c++;
}
return c;
}
public static void main(String args[]){
Scanner scanner=new Scanner(System.in);
String string=scanner.nextLine();
String s[]=string.split(",");
int c[]=new int[s.length];
for(int i=0;i<c.length;i++) {
c[i]=Integer.parseInt(s[i]);
}
int p=0;
for(int i=0;i<c.length;i++) {
p=p^c[i];
}
if(p==0) {System.out.println(1); return;} //p为0必败
else {
int i=0;
for( i=0;i<c.length;i++) {
if(getHigh(p)==getHigh(c[i])) { //拿到合法移动
p=p^c[i];
break;
}
}
int res=0;
res=c[i]-p;//计算需要拿走的数量
System.out.println((char)('A'+i)+","+res);
}
}
}