题目比较经典:
2006 年百度之星程序设计大赛初赛题目 3
变态的比赛规则
为了促进各部门员工的交流,百度 (baidu) 举办了一场全公司范围内的 " 拳皇友谊赛 " ,负责组织这场比赛的是百度的超级 " 拳皇 " 迷 W.Z. W.Z 不想用传统的淘汰赛或者循环赛的方式,而是自己制定了一个比赛规则。
由于一些员工(比如同部门或者相临部门员工)平时接触的机会比较多,为了促进不同部门之间的交流, W.Z 希望员工自己组成不同组。不同组之间的每两个人都会进行一场友谊赛而同一组内的人则之间不会打任何比赛。
比如 4 个人,编号为 1--4, 如果分为两个组并且 1,2 一个组, 3 , 4 一个组,那么一共需要打四场比赛: 1 vs 3,1 vs 4,2 vs 3,2 vs 4. 而如果是 1,2,3 一组, 4 单独一组,那么一共需要打三场比赛 : 1 vs 4,2 vs 4,3 vs 4.
很快 W.Z 意识到,这样的比赛规则可能会让比赛的场数非常多。 W.Z 想知道如果有 N 个人 , 通过上面这种比赛规则,总比赛场数有可能为 K 场吗?比如 3 个人,如果只分到一组则不需要比赛,如果分到两组则需要 2 场比赛 , 如果分为三组则需要 3 场比赛。但是无论怎么分都不可能只需要 1 场比赛。
相信作为编程高手的你一定知道该怎么回答这个问题了吧? 那么现在请你帮助 W.Z 吧。
输入
每行为一组数据,包含两个数字 N, K 。 (0<N<=500, K>=0)
输出
对输入的 N,K 如果 N 个员工通过一定的分组方式可能会一共需要 K 场比赛,则输出 "YES", 否则输出 "NO", 每组数据占一行。
我看到一个前辈的代码非常简洁,而且我测试了n个数结果都正确,但是鄙人愚笨,没理解代码,希望能有高人指点迷津: 代码如下:
public class Demo8 { public static void Func(int N, int K){ if(K==0){ System.out.println("Yes!"); System.exit(1); } for(int i = 1;i<N;i++) Func(N-i, K-i*(N-i)); } public static void main(String[]args){ int N, K; System.out.println("请输入参加人数和比赛场数:"); Scanner s = new Scanner(System.in); N=s.nextInt(); K=s.nextInt(); Func(N, K); System.out.println("No!"); } }
请不吝赐教! 新人,没啥豆子,见谅!
典型的深度优先遍历DFS解法。
由于每个队员之和队友之外的人进行PK,那么本题首先需要考虑的是怎么分组。那么该如何分组呢?由于分组数未知,所以我们需要一一尝试,但是我们能够给出分组的上界2^(n-1)。
我们不妨设需要进行A个分组,每个小组的成员个数分别为{M1,M2,...,MA}。那么总共需要进行多少场比赛呢?
对于第一小组:M1*(M2+M3+...+MA)
对于第二小组:M2*(M1+M3+...+MA)
...
对于第j小组: Mj*(M1+M2+...+M(j-1)+M(j+1)+...+MA)
...
对于第A小组: MA*(M1+M2+...+M(A-1))
从上面的式子可以看出,在单独计算各个小组需要进行的比赛场次时,都有重复的元组。在计算总共进行的场次时需要将重复的场次进行剔除。
观察上面的式子,可以看到在计算第二小组的场次时,第一小组已经计算过一次了,所以第二小组的场次应该为:
M2*(M3+M4+...+MA)
同理,在计算第3小组的场次时,其成员和第一以及第二小组进行比赛的场次需要剔除,即
M3*(M4+M5+...+MA)
...
综合起来,第j个小组需要进行的比赛场次为Mj*第j小组之后所有小组成员总数。
ok,场次的问题应该清楚了,那么现在的问题是如何分组。一共有N个队员,那么一共能分成多少个小组呢?典型的排列组合问题,总分法有2^(n-1)种。虽然分组是没有顺序的,但是从上面的分析我们可以看出,分组顺序并不影响总场次的计算。
假设一共分A组,那么就需要进行A次分组。
第一次 ,找到第一小组,从N个相同的球中挑出M1个, 那么比赛场次为 M1*(N-M1)。 记 R1 = N-M1;
第二次,找到第二小组,此时还有R1 个成员没有分组,挑出M2个队员,比赛场次为 M2*(R1-M2)。那么有R2 = R1-M2;
。。。
第A次,剩余RA-1个队员,此时将所有剩余的MA个成员组成一个队。 比赛场次为 MA* (MA-MA)=0.
将所有的场次相加得到和Sum,比较Sum和K的值,如果相等,那么当前的分组即为一种可行的分组。否则,需要重新进行分组。
经过上面的分析可以看出,分组和场次计算是有递归的规律的。
public static void Func(int N, int K){ // N: 剩余未分组队员的个数, K:还需要进行的场次数 if(K==0){ // 这里有点取巧,即当K==0 的时候,就不再继续分组了,剩余的所有队员组成一个组即可。 System.out.println("Yes!"); System.exit(1); } for(int i = 1;i<N;i++) Func(N-i, K-i*(N-i)); }
没啥豆子没关系,以恢复就有豆子,。。。。