副本是游戏里的一个特色玩法,主要为玩家带来装备、道具、游戏资源的产出,满足玩家的游戏进程。
在 MMORPG《最终幻想14》里,有一个攻略人数最大达到 56 人的副本“巴尔德西昂兵武塔”,因为有在副本里死亡不能复活、机制比较整蛊等特点,一度被玩家视作洪水猛兽。
在副本的开始,我们会遇到第一个难关:攻略的玩家要分为两组,同时讨伐副本 BOSS “欧文”和“亚特”。
已知以下信息:
- 玩家会组成 6 支队伍进入副本,其中第 i 队有 Vi 位玩家(i=1,⋯,6)。
- 每支队伍可能会有一些特殊角色:MT(主坦克)、工兵(负责探测陷阱)和指挥(负责指挥玩家)。
我们的任务是合理安排玩家的分组,以最大程度增加副本通过概率。分组的原则如下:
- 要将所有队伍分成 2 组,每支队伍必须且仅属于其中一组;
- 每组必须有至少一个 MT(主坦克)。
如果满足上述原则的分组方案不唯一,则按照下列规则确定唯一解:
- 优先选择每组有至少一个指挥和至少一个工兵的方案;
- 如果规则 1 无法满足,则优先选择每组至少有一个指挥的方案;
- 如果所有方案都不满足规则 2,或经过前 2 个规则筛选后,分组方案仍不唯一,则选择两边人数尽可能接近(即两边人数差尽可能小)的方案;
- 如果满足规则 3 的方案还不唯一,选择讨伐“欧文”的人数比讨伐“亚特”的人数更多的方案;
- 如果满足规则 4 的方案还不唯一,选择讨伐“欧文”的队伍编号方案中最小的一个。
注:一个队伍编号方案 A={a1<⋯<am} 比 B={b1<⋯<bn} 小,当且仅当存在 1≤k≤min(m,n) 使得 ai=bi 对所有 0<i<k 成立,且 ak<bk。
本题就请你给出满足所有分组原则的分配方案。
输入格式:
输入第一行给出 6 支队伍的玩家数量,即 6 个非负整数 Vi (0≤Vi≤8,1≤i≤6)。队伍人数为 0 时表示队伍不存在。
随后 6 行,按队伍编号顺序,每行给出一支队伍的特殊角色,格式为 ABC
,其中 A
对应 MT,B
对应工兵,C
对应指挥。三种角色对应取值 0 或 1,0 表示没有该角色,1 表示有。
注:由于可能存在一人兼任多个特殊角色的情况,所以一支队伍中的特殊角色数量有可能大于该队伍的玩家数量。
输出格式:
输出分两行,第一行输出讨伐“欧文”的队伍编号,第二行输出讨伐“亚特”的队伍编号。同一行中的编号按升序输出,以 1 个空格分隔,行首尾不得有多余空格。
如果不存在合法的方案,输出GG
。
输入样例1:
6 8 7 5 3 0
010
101
110
001
111
000
输出样例1:
2 3
1 4 5
输入样例2:
6 8 7 5 3 0
010
101
010
001
011
000
输出样例2:
GG
代码长度限制 16 KB
时间限制 400 ms
内存限制 64MB
题解:
这道题好像大家都觉得比较简单,但是我觉得并不简单。。。这个解法是得益于这个博主的题解。6个队伍我们要随机分配到两个集合中(欧文和亚特),我们要做的是选出一个最优方案满足题目中的原则和5条规则。其实每次给出的数据数量都是固定的,每次都是分配到两个集合,每次都有6支队伍,那我们就可以列举出所有队伍组合,然后通过原则筛选,筛选后的再通过5个规则进行排序,在同等规则一样的话就用下个规则继续比!最后排序完第一个就是最优方案!说实话我觉得这个思路是真的妙啊。现在第一件事就是求出所有队伍组合,那有多少种队伍组合呢?一共有种,根据题目的要求,每个集合中至少有一支队伍,所以集合里最少有1支队伍,最多有5支队伍,这是个组合问题,相信大家都很容易理解。现在有个问题就是,我们怎么求出这62种组合?这是本题解的精华所在,我现在也没搞懂它的原理所在。
Set<String> set = new LinkedHashSet<String>();
for (int i = 1; i <= 62; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 1; j <= 6; j++) {
if (((1<<(j-1))&i) != 0) {
sb.append(j);
}
}
set.add(sb.toString());
}
这段代码的作用就是把6个队伍分配到2个集合的62种分配方案全部求出来,最后都装到set里。其中((1<<(j-1))&i) != 0就是精华所在,我不知道为什么,我对位运算不熟,不知道这个用法是哪个知识点,总之它就是可以每次过滤出不一样的方案,经过这62次循环,我们刚好得到62个方案,我还特意用set去重,实际上每次都是新的方案,没有重复!我们排下序看下set里面的情况:
可以看到,刚好62个,每个都不重复,而且也是满足至少有一支队伍的要求,所以最长的不会超过5。有知道这个用法是怎么回事的朋友请教教我,我真的好想知道是怎么回事。
现在我们求出了62种方案,就可以对这些方案进行过滤了。为了方便排序,我把方案抽象为一个类,类里面的属性都是为了判断是否满足5个规则。然后在类里写了排序规则,就是题目的5个规则的体现,具体见代码。
我们先按原则进行排除,符合原则的加入一个集合进行排序,最后排完序第一个就是最优解。如果集合里一个方案也没有,就输出GG。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) {
InputReader in = new InputReader();
List<Scheme> schemes = new ArrayList<Scheme>();//用来存放进行排序的方案
int[][] ranks = new int[7][4];//二维数组保存队伍信息,每一行就是一个队伍
for (int i = 1; i < 7; i++)
ranks[i][0] = in.nextInt();//0列保存队伍人数
for (int i = 1; i < 7; i++) {
String specialRoles = in.next();
ranks[i][1] = specialRoles.charAt(0) - 48;//1列保存队伍的MT数量
ranks[i][2] = specialRoles.charAt(1) - 48;//2列保存队伍的工兵数量
ranks[i][3] = specialRoles.charAt(2) - 48;//3列保存队伍的指挥数量
}
for (int i = 1; i <= 62; i++) {//这个循环用来求所有方案
Scheme scheme = new Scheme();
scheme.owenTeamNum = new LinkedList<Integer>();
scheme.artTeamNum = new LinkedList<Integer>();
for (int j = 1; j < 7; j++) {//这个循环就是具体确定这个方案包含哪些队伍
if (ranks[j][0] == 0) //通过题目样例可以知道队伍人数为0直接不要
continue;
if (((1 << (j - 1)) & i) != 0) {//要么给欧文,要么给亚特
scheme.owenTeamNum.add(j);
scheme.owenPeoNum += ranks[j][0];
scheme.owenMT |= ranks[j][1];//我们只需要知道为不为0,所以可以用|=
scheme.owenEng |= ranks[j][2];
scheme.owenCom |= ranks[j][3];
} else {
scheme.artTeamNum.add(j);
scheme.artPeoNum += ranks[j][0];
scheme.artMT |= ranks[j][1];
scheme.artEng |= ranks[j][2];
scheme.artCom |= ranks[j][3];
}
}
//通过原则进行筛选,不符合原则的不能加入集合进行排序
if (scheme.owenTeamNum.size() == 0 || scheme.artTeamNum.size() == 0 || scheme.owenMT == 0 || scheme.artMT == 0)
continue;
//计算方案的其他属性
scheme.difference = Math.abs(scheme.owenPeoNum - scheme.artPeoNum);//欧文和亚特的人数差
scheme.greaterThan = scheme.owenPeoNum > scheme.artPeoNum ? true : false;//欧文人数是否多于亚特
scheme.MTBoth = (scheme.owenMT & scheme.artMT) != 0 ? true : false;//欧文和亚特是否都有MT
scheme.engComBoth = (scheme.owenEng & scheme.artEng) != 0 && (scheme.owenCom & scheme.artCom) != 0 ? true : false;//欧文和亚特是否都有工兵和指挥
scheme.comBoth = (scheme.owenCom & scheme.artCom) != 0 ? true : false;//欧文和亚特是否都有指挥
schemes.add(scheme);//最后加入集合等待排序
}
Collections.sort(schemes);//进行排序!
if (schemes.size() > 0) {//如果集合里一个都没有,说明都不符合原则,输出GG
int i = 0;
for (int rank : schemes.get(0).owenTeamNum) {//输出欧文
if (i == 0) {
System.out.print(rank);
i++;
}else {
System.out.print(" " + rank);
}
}
System.out.println();
i = 0;
for (int rank : schemes.get(0).artTeamNum) {//输出亚特
if (i == 0) {
System.out.print(rank);
i++;
}else {
System.out.print(" " + rank);
}
}
} else {
System.out.print("GG");
}
}
}
class Scheme implements Comparable<Scheme>{//方案类,每个方案都包含一个欧文集合和一个亚特集合
public int owenPeoNum;//欧文组的总人数
public int artPeoNum;//亚特组的总人数
public LinkedList<Integer> owenTeamNum;//欧文组的队伍编号
public LinkedList<Integer> artTeamNum;
public int owenMT;//欧文组的主坦克数量
public int artMT;
public int owenEng;//欧文组的工兵数量
public int artEng;
public int owenCom;//欧文组的指挥数量
public int artCom;
public int difference;//两组人数差
public boolean greaterThan;//true:欧文组人数大于亚特组人数,反之false
public boolean MTBoth;//true:两组都有主坦克,反之false
public boolean engComBoth;//true:两组都有工兵和指挥,反之false
public boolean comBoth;//true:两组都有指挥,反之false
public int compareTo(Scheme o) {
if (this.engComBoth && !o.engComBoth) {//规则1,两组集合都有工兵和指挥的方案排在前面
return -1;//返回-1才是排在前面哦,返回1是排在后面
}else if (this.engComBoth == o.engComBoth) {//如果都一样进入规则2
if (this.comBoth && !o.comBoth) {//规则2,两组集合都有指挥的方案排在前面
return -1;
}else if (this.engComBoth == o.engComBoth) {//如果都一样进入规则3
if (this.difference < o.difference) {//规则3,人数差小的方案排在前面
return -1;
}else if (this.difference == o.difference) {//如果都一样进入规则4
if (this.greaterThan && !o.greaterThan) {//规则4,欧文比亚特人数多的方案排在前面
return -1;
}else if (this.greaterThan == o.greaterThan) {//如果两个方案都是欧文比亚特人数多或者少那就进入规则5
//规则5可能不太好理解,就是两个方案的欧文队伍编号一个一个比,谁先比对方小的就算比较小
for (int i = 0; i < this.owenTeamNum.size() && i < o.owenTeamNum.size(); i++)
if (this.owenTeamNum.get(i) != o.owenTeamNum.get(i))
return this.owenTeamNum.get(i) - o.owenTeamNum.get(i);
//因为是两个方案,所以可能出现1 2,1 2 3这种情况,这个时候按队伍数少的算小
return this.owenTeamNum.size() - o.owenTeamNum.size();
}else {
return 1;
}
}else {
return 1;
}
}else {
return 1;
}
}else {
return 1;
}
}
}
class InputReader{
BufferedReader buf;
StringTokenizer tok;
public InputReader() {
buf = new BufferedReader(new InputStreamReader(System.in));
}
boolean hasNext() {
while (tok == null || !tok.hasMoreElements()) {
try {
tok = new StringTokenizer(buf.readLine());
} catch (IOException e) {
return false;
}
}
return true;
}
String next() {
if (hasNext())
return tok.nextToken();
return null;
}
int nextInt() {
return Integer.parseInt(next());
}
String nextLine() throws IOException {
return buf.readLine();
}
}