题目描述
马上又到一年一度的网易校招了,HR找到小Y,告诉他要开始准备今年的机考题目了。小Y和小伙伴们日以继夜、废寝忘食地出好了N道题目,并且给每道题目打了难度1~5, 同时 给每道题目标记了相应的tag(一道题目可能有多个tag),表示这道题目所考察的知识点。
由于机考时间有限,小Y需要从N到候选题目中挑选出M道题目组成最终的考卷,为了挑选最合适的同学,并且使得题目具备一定的区分度,同时考察的知识面尽量广,这M道题目还需要满足下列条件:
(1)所有M道题目的总难度不超过指定的值L。
(2)所有M道题目的考察点都没有重复,即所有的tag都没有重复。
(3)为了具备区分度,难度最低的题目相比难度最高的题目的难度差不低于S。
小Y想知道满足上述条件的总方案数有多少种,你能告诉他吗?
输入描述:
每个输入数据包含多个测试点。
第一行为测试点的个数T(T<=10)。
每个测试点第1行包含4个数字:N(1<N<=16),M(1<M<=N),L(0<=L<=100),S(0<=S<=4)。
然后是N行,每行代表一道题的信息。每行首先是题目的难度X(1<=X<=5),然后一个整数P(0<P<=5)表示这道题目的tag数目,后面接着P个以空格间隔的字符串表示tag,每个字符串均由小写字母组成,长度不超过10,保证每道题不会包含一样的tag。
输出描述:
对于每个测试点,输出一行,表示总的方案数。
示例
输入
2
5 2 5 1
1 2 string dfs
1 1 heap
2 1 dfs
3 2 math search
4 1 dp
5 2 5 0
1 2 string dfs
1 1 heap
2 1 dfs
3 2 math search
4 1 dp
输出
6
7
解题思路
该题的主要难点在于如何遍历从N道题中选择M道题的所有情况,对于每种情况再进行条件满足判断。
采用动态规划的思路进行遍历所有情况,再判断是否满足总难度、最大难度差和tag重复问题,tag重复问题采用Set集合类进行判断。
主如下:
import java.util.ArrayList;
import java.util.Scanner;
import java.util.TreeSet;
public class DynamicProgram {
// 结果集
static int[] result;
// 需求集
static String[][] demands;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int all = Integer.parseInt(scanner.nextLine().trim());
// 成员初始化
result = new int[all];
demands = new String[all][];
// 存所有测试点的所有题目信息
ArrayList<String[][]> conditions = new ArrayList<>();
for (int i = 0; i < all; i++) {
// 存第i个测试点的需求
demands[i] = scanner.nextLine().trim().split(" ");
// 存第i个测试点的所有题目信息
String[][] condition = new String[Integer.parseInt(demands[i][0])][];
for (int j = 0; j < Integer.parseInt(demands[i][0]); j++) {
condition[j] = scanner.nextLine().trim().split(" ");
}
conditions.add(condition);
}
// 遍历所有测试点,进行条件判断
for (int i = 0; i < all; i++) {
int[] hasCondition = new int[Integer.parseInt(demands[i][1])];
combine(conditions.get(i), conditions.get(i).length, Integer.parseInt(demands[i][1]), hasCondition, i);
}
for (int out : result)
System.out.println(out);
}
/**
*
* @param condition 该测试点下的所有条件
* @param num 该测试点下的条件总数
* @param m 第i种情况下选择的条件,长度为M的个数
* @param hasCondition 第i种情况下选择的条件,长度为M的个数
* @param demandIndex 第demandIndex个测试点,用于在满足条件时,第demandIndex个测试点的结果加一
*/
public static void combine(String[][] condition, int num, int m, int[] hasCondition, int demandIndex) {
for (int i = num; i >= m; i--) {
hasCondition[m - 1] = i - 1;
if (m > 1) {// m>1,表示还未选择m个条件,继续迭代
combine(condition, i - 1, m - 1, hasCondition, demandIndex);
} else {// 满足条件m个,进行条件满足判断
checkCondition(condition, hasCondition, demandIndex);
}
}
}
/**
* 判断第i种情况下是否满足条件,满足则result加一
* @param condition 该测试点下的所有条件
* @param hasCondition 第i种情况下选择的条件,长度为M的个数
* @param demandIndex 第demandIndex个测试点,用于在满足条件时,第demandIndex个测试点的结果加一
*/
public static void checkCondition(String[][] condition, int[] hasCondition, int demandIndex) {
int l = Integer.parseInt(demands[demandIndex][2]);
int s = Integer.parseInt(demands[demandIndex][3]);
TreeSet<String> tags = new TreeSet<>();
// 该情况下的总难度
int sum = 0;
// 最小难度值
int minDifficulty = Integer.parseInt(condition[hasCondition[0]][0]);
// 最大难度值
int maxDifficulty = Integer.parseInt(condition[hasCondition[0]][0]);
for (int i = 0; i < hasCondition.length; i++) {// 遍历该情况下的所有条件
String[] cond = condition[hasCondition[i]];
int difficulty = Integer.parseInt(cond[0]);
// 比较难度值是否最小
minDifficulty = Math.min(minDifficulty, difficulty);
// 比较难度值是否最大
maxDifficulty = Math.max(maxDifficulty, difficulty);
sum += Integer.parseInt(cond[0]);
// 采用Set集合类判断tag是否有重复
int tagNum = Integer.parseInt(cond[1]);
for (int j = 0; j < tagNum; j++) {
if (tags.contains(cond[j + 2])) {
return;
} else {
tags.add(cond[j + 2]);
}
}
}
// 判断总难度是否合格
if (sum > l)
return;
// 判断最大难度差是否合格
if (maxDifficulty - minDifficulty < s) {
return;
}
// 满足条件,第demandIndex个测试点的结果加一
result[demandIndex]++;
}
}