第一行输入两个整数
和
代表城市个数、游戏轮数,数据保证
一定是 10 的倍数。
第二行输入
个整数
表示第一次到达城市
到
可以获得的金币数量。
在一行上输出一个整数,代表小歪能获得的最多金币数量。
10 1 -1 -1 2 3 4 -9 -9 -1 3 -1
9
最优的方法是:
● 第 1 步:使用跳跃 3 的卡牌,从 0 跳到 3 ,获得 3 枚金币;
● 第 2 步:使用跳跃 1 的卡牌,从 3 跳到 4 ,获得 4 枚金币,共有 7 枚金币;
● 第 3 步:使用跳跃 4 的卡牌,从 4 跳到 8 ,获得 3 枚金币,共有 10 枚金币;
● 第 4 步:使用跳跃 2 的卡牌,从 8 跳到 0 ,获得 -1 枚金币,共有 9 枚金币。
10 2 -1 -1 2 3 4 -9 -9 -1 3 -1
10
最优的方法是:
● 第 1 步:使用跳跃 3 的卡牌,从 0 跳到 3 ,获得 3 枚金币;
● 第 2 步:使用跳跃 1 的卡牌,从 3 跳到 4 ,获得 4 枚金币,共有 7 枚金币;
● 第 3 步:使用跳跃 4 的卡牌,从 4 跳到 8 ,获得 3 枚金币,共有 10 枚金币;
● 第 4 步:使用跳跃 2 的卡牌,从 8 跳到 0 ,获得 -1 枚金币,共有 9 枚金币。
● 第 5 步:使用跳跃 2 的卡牌,从 0 跳到 2 ,获得 2 枚金币,共有 11 枚金币;
● 第 6 步:使用跳跃 1 的卡牌,从 2 跳到 3 ,获得 0 枚金币(不是第一次到达),共有 11 枚金币;
● 第 7 步:使用跳跃 4 的卡牌,从 3 跳到 7 ,获得 -1 枚金币,共有 10 枚金币;
● 第 8 步:使用跳跃 3 的卡牌,从 7 跳到 0 ,获得 0 枚金币(不是第一次到达),共有 10 枚金币。
深度搜索+剪枝。
首先,将n个城市进行划分,得到n/10个城市子序列。由于城市序列是循环的,故而当k > n/10且k % (n/10) > 0 时,会重复遍历前面子序列。所以分类一下,前k % (n/10) 个子序列需要循环 k / (n/10)+1 次,后面子序列的循环次数减一次。
再来看每个长度为10的子序列,首先由于1+2+3+4=10,从起点0出发,一定会到达终点10,但3个中间城市的组合是不确定的,总共有4!=24种组合方式。
那么可以将问题转换一下,若需要循环遍历子序列m次,相当于从24个集合里选取m个(允许重复选取),使其m个集合的并集的元素和最大化。为了兼容重复选取,问题可以转化为选取的集合数量不超过m个。
那么现在寻找单个子序列上的最大值,就变成了一个常规的搜索问题,纯粹暴力搜索的复杂度为24!。但是由于最多会有10000个子序列,每次完全的暴力搜索必然超时,故而需要剪枝,剪枝策略为如果再次发现已经被找到的并集,则跳出该分支。
实现代码上做了一些特殊处理,例如使用回溯法生成24种排列组合,实际上也可以直接人工列出。
对于3个中间城市,由于其序号始终位于1-9之间,我选择使用整数mask来表示集合,第i个比特位为1表示第i个城市处于该集合,这样可以直接用位运算求并集,也便于记录搜索的状态。
import java.util.Scanner; import java.util.*; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { static int[][] paths = new int[24][4]; static int[] masks = new int[24]; static int index = 0; static long[] max_gains = new long[26]; static int max_depth = 0; static boolean[] visited = new boolean[1024]; // 回溯法生成24种组合 private static void backTrace( int[] current, int cnt, boolean[] used){ if(cnt==4) { for(int j=0; j<4; j++){ paths[index][j] = current[j]; } index ++; } for(int i=0; i<4; i++){ if(used[i]){ continue; } else { current[cnt] = i+1; used[i] = true; backTrace(current, cnt + 1, used); used[i] = false; } } } // 深度搜索 private static void dfs(int[] money, int mask, long cur_gain, int cur_index, int depth){ if(depth >= max_depth) return; int chosen_mask = masks[cur_index]; int new_mask = chosen_mask | mask; if(!visited[new_mask]){ visited[new_mask] = true; int op = 1; long new_gain = 0; for(int i=0; i<9; i++){ if((op & new_mask) > 0){ new_gain += money[i]; } op = op << 1; } max_gains[depth+1] = Math.max(max_gains[depth+1],new_gain); for(int j=cur_index + 1; j<24; j++){ dfs(money, new_mask, new_gain, j, depth+1); } } return; } public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int k = in.nextInt(); int stage_num = n / 10; int[][] arr = new int[stage_num][10]; arr[stage_num-1][9] = in.nextInt(); for(int i=0; i<stage_num-1; i++){ for(int j=0; j<10; j++) arr[i][j] = in.nextInt(); } for(int j=0; j<9; j++){ arr[stage_num-1][j] = in.nextInt(); } int[] current = new int[4]; boolean[] used = new boolean[4]; backTrace(current, 0, used); for(int i=0; i<24; i++){ int cur_index = 0; int mask = 0; for(int j=0; j<3; j++){ cur_index += paths[i][j]; mask |= 1 << (cur_index-1); } masks[i] = mask; } long res = 0; int mid = 0; if(k % stage_num == 0){ max_depth = k / stage_num; mid = stage_num; } else { max_depth = k / stage_num + 1; mid = k % stage_num; } max_depth = Math.min(25, max_depth); long min_inf = -0x7fffffffffffffffL-1; for(int i=0; i<stage_num; i++){ if(i==mid) max_depth -= 1; if(max_depth <= 0) continue; for(int j=0; j<1024; j++){ visited[j] = false; } for(int j=0; j<=max_depth ; j++){ max_gains[j] = min_inf; } for(int j=0; j<24; j++){ dfs(arr[i], 0, min_inf, j, 0); } long stage_max_gain = min_inf; for(int j=1; j<=max_depth; j++){ stage_max_gain = Math.max(stage_max_gain, max_gains[j]); } res += stage_max_gain + arr[i][9]; } System.out.print(res); } }