文字显示
一直在犹豫这道题是贪心还是动态规划,想了很久,觉得贪心是成立的,后来事实证明确实是成立的。
处理方法是,如果当前是字母,那么很简单,这一行放得下就放在这一行,放不下就新开一行,只需要用一个变量记录一下当前这一行占用了多少宽度就行。
如果当前处理的是标点,那么找到标点结束的地方,把这一整段标点作为一个整体考虑,要么全放在当前行的末尾,如果当前行放不下,那么这一整段就要放到下面一行。
由于这一段都是标点,所以不能从中间截断,否则就势必会有出现在行首的标点,因此,没有其他情况需要考虑。
对于能直接放在当前行末尾的,那么直接放上去,如果放不下,就要放下一行,但是下一行的行首不能是标点,这意味着要从当前行取出一个字母放到下一行的行首,由于标点只作为一个整体考虑,因此,当前行最后一个一定是字母,不会是标点,所以直接判断所有标点长度加 1 以后的大小是不是够放下一行。
任何情况下出现放不下,直接给出 impossible
。
对于换行符,由于是无条件换行,所以可以在开始的时候就直接以换行符作为 split,每一段分别计算最短行数,累加得到答案。
特别需要注意的是,\n
的转义是 \\\\n
,而不是 \\n
。(我以为 \
的转义是 \\
,结果交了一次通过 0% 的样例,修改后再交就是 AC)
import java.util.HashSet; import java.util.Scanner; /* 共提交 2 次,第 1 次通过 0% 的样例,第 2 次通过 100% 的样例 */ public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int T = in.nextInt(); for (int t = 0; t < T; t++) { int ans = 0; int x = in.nextInt(); int y = in.nextInt(); String[] ss = in.next().split("\\\\n"); /* 这里是 \\\\n 表示 \n 的转义,不是 \n 或者 \\n */ try { for (String s : ss) { if (s.length()==0) continue; ans += solve(x,y,s); } } catch (Exception e) { System.out.println("impossible"); continue; } System.out.println(ans); } in.close(); } private static int solve(int x, int y, String s) throws Exception { final HashSet<Character> SYMBOL = new HashSet<Character>(); SYMBOL.add(','); SYMBOL.add('.'); SYMBOL.add('!'); SYMBOL.add('?'); int len = s.length(); int index = 0; int yy = 0; int ret = 0; while (index < len) { char c = s.charAt(index); if (!SYMBOL.contains(c)) { if (yy + x <= y) { yy += x; } else { ret++; yy = x; } index++; } else { int begin = index; while (index < len && SYMBOL.contains(s.charAt(index))) index++; int end = index; if (yy + x * (end - begin) <= y) { yy += x * (end - begin); } else if (x * (end - begin + 1) <= y){ yy = x * (end - begin + 1); ret++; } else { throw new Exception(); } } } return ret + 1; } }
特殊键盘
最短路径。
这道题看出是最短路径以后就非常简单了,由于只有 26 个字母,所以直接无脑 Floyd 跑三重循环。
注意自己到自己的距离是 1,遇到一个 shift 就加 1。或者也可以自己到自己的距离为 0,最后得到的最短路长度加上原始文章的长度(因为每个按键至少按一次)。
计算出最短路径以后,把损坏的按键那一行的点全部置为无穷大。
计算最短路,如果最短路结果为无穷大,则这篇文章不可能被输出。
import java.util.HashSet; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int T = in.nextInt(); for (int t = 0; t < T; t++) { int n = in.nextInt(); int[][] keyboard = new int[26][26]; final int INFINITY = 1000; for (int i = 0; i < keyboard.length; i++) for (int j = 0; j < keyboard[i].length; j++) { if (i == j) keyboard[i][j] = 0; else keyboard[i][j] = INFINITY; } for (int i = 0; i < n; i++) { int x = in.next().charAt(0) - 'a'; int y = in.next().charAt(0) - 'a'; keyboard[x][y] = 1; } int m = in.nextInt(); HashSet<Character> broken = new HashSet<Character>(); for (int i = 0; i < m; i++) { char c = in.next().charAt(0); broken.add(c); } for (int k = 0; k < keyboard.length; k++) for (int i = 0; i < keyboard.length; i++) for (int j = 0; j < keyboard.length; j++) if (keyboard[i][j] > keyboard[i][k] + keyboard[k][j]) keyboard[i][j] = keyboard[i][k] + keyboard[k][j]; for (char c : broken) for (int j = 0; j < keyboard.length; j++) keyboard[c - 'a'][j] = INFINITY; String s = in.next(); int len = s.length(); int ans = len; for (int index = 0; index < len; index++) { int c = s.charAt(index) - 'a'; int who = c; for (int i = 0; i < keyboard.length; i++) if (keyboard[i][c] < keyboard[who][c]) who = i; if (keyboard[who][c] == INFINITY) ans = -1; else ans += keyboard[who][c]; if (ans == -1) break; } System.out.println(ans); } in.close(); } }
AOE
这道题目我没做出来,没能转化成可以做的题。
我在考虑的是,所有点两两组队,计算两点之间的距离,如果小于等于直径,求两点中点,以中点画圆,看半径覆盖范围内最大有多少点。
后来证明这个思路是错的。考试结束后问 zerol,他说要转化一下,把一个圆覆盖多少个点,转化为:以每个点为圆心,画半径为 r 的圆,问所有圆之间会有相互重叠的地方,问哪个地方被最多的圆重叠,那么站在这个区域内施法,就可以攻击到最多的目标。
圆与圆相互重叠最厚的那儿就是施法点,由于圆的边界也是可以攻击到目标的,所以只需要遍历所有圆的交点,看哪个点最多个圆相交。