攻略分队(java实现)

副本是游戏里的一个特色玩法,主要为玩家带来装备、道具、游戏资源的产出,满足玩家的游戏进程。

在 MMORPG《最终幻想14》里,有一个攻略人数最大达到 56 人的副本“巴尔德西昂兵武塔”,因为有在副本里死亡不能复活、机制比较整蛊等特点,一度被玩家视作洪水猛兽。

在副本的开始,我们会遇到第一个难关:攻略的玩家要分为两组,同时讨伐副本 BOSS “欧文”和“亚特”。

已知以下信息:

  1. 玩家会组成 6 支队伍进入副本,其中第 i 队有 Vi​ 位玩家(i=1,⋯,6)。
  2. 每支队伍可能会有一些特殊角色:MT(主坦克)、工兵(负责探测陷阱)和指挥(负责指挥玩家)。

我们的任务是合理安排玩家的分组,以最大程度增加副本通过概率。分组的原则如下:

  1. 要将所有队伍分成 2 组,每支队伍必须且仅属于其中一组;
  2. 每组必须有至少一个 MT(主坦克)。

如果满足上述原则的分组方案不唯一,则按照下列规则确定唯一解:

  1. 优先选择每组有至少一个指挥和至少一个工兵的方案;
  2. 如果规则 1 无法满足,则优先选择每组至少有一个指挥的方案;
  3. 如果所有方案都不满足规则 2,或经过前 2 个规则筛选后,分组方案仍不唯一,则选择两边人数尽可能接近(即两边人数差尽可能小)的方案;
  4. 如果满足规则 3 的方案还不唯一,选择讨伐“欧文”的人数比讨伐“亚特”的人数更多的方案;
  5. 如果满足规则 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个规则进行排序,在同等规则一样的话就用下个规则继续比!最后排序完第一个就是最优方案!说实话我觉得这个思路是真的妙啊。现在第一件事就是求出所有队伍组合,那有多少种队伍组合呢?一共有C_{6}^{1}+C_{6}^{2}+C_{6}^{3}+C_{6}^{4}+C_{6}^{5}=62种,根据题目的要求,每个集合中至少有一支队伍,所以集合里最少有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();
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值