PREV-46 试题 历届试题 填字母游戏(DFS+博弈论)
【问题描述】
小明经常玩 LOL 游戏上瘾,一次他想挑战K大师,不料K大师说:
“我们先来玩个空格填字母的游戏,要是你不能赢我,就再别玩LOL了”。
K大师在纸上画了一行n个格子,要小明和他交替往其中填入字母。
并且:
1. 轮到某人填的时候,只能在某个空格中填入L或O
2. 谁先让字母组成了“LOL”的字样,谁获胜。
3. 如果所有格子都填满了,仍无法组成LOL,则平局。
小明试验了几次都输了,他很惭愧,希望你能用计算机帮他解开这个谜。
【输入格式】
第一行,数字n(n<10),表示下面有n个初始局面。
接下来,n行,每行一个串,表示开始的局面。
比如:****** , 表示有6个空格。“L****”, 表示左边是一个字母L,它的右边是4个空格。
【输出格式】
要求输出n个数字,表示对每个局面,如果小明先填,当K大师总是用最强着法的时候,小明的最好结果。
1 表示能赢
-1 表示必输
0 表示可以逼平
【样例输入】
4
***
L**L
L**L***L
L*****L
【样例输出】
0
-1
1
1
思路: 博弈论 + 记忆化搜索。
这道题的边界条件:
-
如果串中有LO或LL或*OL,则现在走的玩家必赢。
-
如果串中没有*,则平局。
用一个map<string, int> m记录每个局面的结果,这样就不会重复计算。
Code:
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
static Map<String, Integer> mp = new HashMap<String, Integer>();
static String[] ch = {
"L", "O" };
static int dfs(String str) {
//如果集合里面有这个字符串,就直接返回结果
if (mp.containsKey(str))
return mp.get(str);
//如果字符串有“LOL”,那么就直接输了
if (str.contains("LOL"))
return -1;
//长度小于3,也就直接平局了
if (str.length() < 3)
return 0;
//没有空格也平局
if (!str.contains("*"))
return 0;
//用来判断是否有可能平局的
boolean ping = false;
int len = str.length();
for (int i = 0; i < len; i++) {
//如果是字符,只需要直接用==,如果是字符串,就要用equals
if (str.charAt(i) == '*') {
//暂存
String tmp = str;
for (int j = 0; j < 2; j++) {
//替换指定字符
str = tmp.substring(0, i) + ch[j] + tmp.substring(i + 1, len);
//存在对手赢的情况,就回溯跳过
if (str.contains("L*L") || str.contains("*OL") || str.contains("LO*")) {
str = tmp.substring(0, i) + "*" + tmp.substring(i + 1, len);
continue;
}
//搜索对手的情况
int next = dfs(str);
//回溯
str = tmp.substring(0, i) + "*" + tmp.substring(i + 1, len);
//对手赢就跳过
if (next == 1)
continue;
else if (next == -1)
return 1;
else if (next == 0)
ping = true;
}
}
}
//存在平局状况
if (ping) {
mp.put(str, 0);
return 0;
}
//反正就是输的情况了
mp.put(str, -1);
return -1;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
sc.nextLine();
for (int i = 0; i < n; i++) {
String str = sc.nextLine();
System.out.println(dfs(str));
}
}
}
PREV-54 试题 历届试题 合根植物(并查集模板)
【问题描述】
w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
【输入格式】
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
【样例输入】
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
【样例输出】
5
【样例说明】
其合根情况参考下图:
思路: 简单并查集模板题。
Code:
import java.util.Scanner;
public class Main {
static int n, m, k;
static int[] root;
static int find(int x) {
while (root[x] != x) {
x = root[x];
}
return x;
}
static void combine(int x, int y) {
int root1 = find(x);
int root2 = find(y);
if (root1 != root2) {
root[root1] = root2;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
m = sc.nextInt();
n = sc.nextInt();
k = sc.nextInt();
root = new int[m*n+10];
for (int i = 1; i <= m * n; i++) {
root[i] = i;
}
for (int i = 1; i <= k; i++) {
int x = sc.nextInt(), y = sc.nextInt();
combine(x, y);
}
int ans = 0;
for (int i = 1; i <= m * n; i++) {
if (root[i] == i)
ans++;
}
System.out.println(ans);
}
}
PREV-55 试题 历届试题 小计算器(模拟+进制转换API)
【问题描述】
模拟程序型计算器,依次输入指令,可能包含的指令有
-
数字:‘NUM X’,X为一个只包含大写字母和数字的字符串,表示一个当前进制的数
-
运算指令:‘ADD’,‘SUB’,‘MUL’,‘DIV’,‘MOD’,分别表示加减乘,除法取商,除法取余
-
进制转换指令:‘CHANGE K’,将当前进制转换为K进制(2≤K≤36)
-
输出指令:‘EQUAL’,以当前进制输出结果
-
重置指令:‘CLEAR’,清除当前数字
指令按照以下规则给出:
数字,运算指令不会连续给出,进制转换指令,输出指令,重置指令有可能连续给出
运算指令后出现的第一个数字,表示参与运算的数字。且在该运算指令和该数字中间不会出现运算指令和输出指令
重置指令后出现的第一个数字,表示基础值。且在重置指令和第一个数字中间不会出现运算指令和输出指令
进制转换指令可能出现在任何地方
运算过程中中间变量均为非负整数,且小于2^63。
以大写的 ‘A’ ~ ‘Z’ 表示 10~35
【输入格式】
第1行:1个n,表示指令数量
第2…n+1行:每行给出一条指令。指令序列一定以’CLEAR’作为开始,并且满足指令规则
【输出格式】
依次给出每一次’EQUAL’得到的结果
【样例输入】
7
CLEAR
NUM 1024
CHANGE 2
ADD
NUM 100000
CHANGE 8
EQUAL
【样例输出】
2040
思路: 这道题的关键在于进制转换,Long.valueOf()这个函数可以把指定字符串转换为想要的进制。
Code:
import java.util.Scanner;
public class Main {
static String op = ""; // 运算符
static int hex = 10; // 进制
static long[] num = new long[3]; // 用来存两个要运算的数
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
sc.nextLine();
for (int i = 1; i <= n; i++) {
String ss = sc.nextLine();
String[] str = ss.split(" "); // 根据空格来分割
operate(str);
}
}
// 指令操作
public static void operate(String[] str) {
switch (str[0]) {
case "NUM":
if ("".equals(op)) {
// Long.valueOf可以把字符串str[1]转换为hex进制的long数值
num[0] = Long.valueOf(str[1], hex);
} else {
num[1] = Long.valueOf(str[1], hex);
num[0] = calcute();
num[1] = 0;
op = "";
}
break;
case "ADD":
op = "ADD";
break;
case "SUB":
op = "SUB";
break;
case "MUL":
op = "MUL";
break;
case "DIV":
op = "DIV";
break;
case "MOD":
op = "MOD";
break;
case "CHANGE":
hex = Integer.parseInt(str[1]);
break;
case "EQUAL":
// 因为有可能是十六进制数,所以要转换为字符串形式,然后还要大写
System.out.println(Long.toString(num[0], hex).toUpperCase());
break;
case "CLEAR":
num[0] = 0;
num[1] = 0;
op = "";
break;
default:
break;
}
}
// 运算
private static long calcute() {
// TODO Auto-generated method stub
long ans = 0;
switch (op) {
case "ADD":
ans = num[0] + num[1];
break;
case "SUB":
ans = num[0] - num[1];
break;
case "MUL":
ans = num[0] * num[1];
break;
case "DIV":
ans = num[0] / num[1];
break;
case "MOD":
ans = num[0] % num[1];
break;
default:
break;
}
return ans;
}
}