A - Leftrightarrow
题目:
给一个包含<
、>
、 =
的字符串,判断其是否为双向性(指字符串左边为 <
,右边为 >
,中间若干个 =
)
思路:
直接判断即可
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
class Main {
private static InputStream is = System.in;
private static final BufferedReader in = new BufferedReader(new InputStreamReader(is));
private static final PrintWriter out = new PrintWriter(System.out);
static void solve() throws IOException {
String s = in.readLine();
if (s.length() <= 2) {
out.print("No");
return;
}
if (s.charAt(0) == '<' && s.charAt(s.length() - 1) == '>') {
for (int i = 1; i < s.length() - 1; i ++) {
if (s.charAt(i) != '=') {
out.print("No");
return;
}
}
out.print("Yes");
} else {
out.print("No");
}
}
public static void main(String[] args) throws IOException {
int t = 1;
// t = Integer.parseInt(in.readLine());
while (t -- > 0) {
solve();
}
in.close();
out.flush();
out.close();
}
}
B - Integer Division Returns
题目:
给一个正数x,输出其除以10的上取整数(不会小于其本身的最小整数)
思路:
- 正整数使用 x + 10 − 1 10 \frac{x + 10 - 1}{10} 10x+10−1 计算
- 负整数则直接 x 10 \frac{x}{10} 10x
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
class Main {
private static InputStream is = System.in;
private static final BufferedReader in = new BufferedReader(new InputStreamReader(is));
private static final PrintWriter out = new PrintWriter(System.out);
static void solve() throws IOException {
long a = Long.parseLong(in.readLine());
if (a >= 0) {
out.println((a + 10 - 1) / 10);
} else {
out.println(a / 10);
}
}
public static void main(String[] args) throws IOException {
int t = 1;
// t = Integer.parseInt(in.readLine());
while (t -- > 0) {
solve();
}
in.close();
out.flush();
out.close();
}
}
C - One Time Swap
题意:
给一个字符串s
,执行以下操作一次:选择一对整数下标 (i, j)
满足 1<=i < j <= len(s)
,使其交换;
求执行操作后能产生的字符串种类
思路:
字符串的长度为1e6,不能暴力枚举;
假设一个字符串由A部分和B部分组成(A部分和B部分之间字母不同,但是内部字母相同),能交换的只有A和B之间组成的字母对,产生的结果为len(A) * len(B)
;而交换两个相同的字母,能产生的结果只有一种,那就是 s
本身(这个很重要,不能忽略,我就是忽略了这个就WA了几次QAQ);
那么统计每个字母出现次数,统计该字母和其他字母能产生的字母对(乘法原理)
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
class Main {
private static InputStream is = System.in;
private static final BufferedReader in = new BufferedReader(new InputStreamReader(is));
private static final PrintWriter out = new PrintWriter(System.out);
static void solve() throws IOException {
String s= in.readLine();
int[] cnt = new int[26];
for (char c : s.toCharArray()) cnt[c- 'a'] ++;
long ans = 0;
for (int i = 0; i < 26; i ++) {
for (int j = i + 1; j < 26; j ++) {
ans += (long) cnt[i] * cnt[j];
}
}
for (int i = 0; i < 26; i ++) { // 统计交换相同字母(仅统计一次)
if (cnt[i] >= 2) {
ans ++;
break;
}
}
out.println(ans);
}
public static void main(String[] args) throws IOException {
int t = 1;
// t = Integer.parseInt(in.readLine());
while (t -- > 0) {
solve();
}
in.close();
out.flush();
out.close();
}
}
D - Tiling
题目:
给一个 H
行 W
列的矩阵,每个单元格的边长都是1,有N张瓷砖,第 i 张瓷砖是一个 Ai * Bi
大小的矩形;
判断是否可能能用这些瓷砖铺满整个矩阵,要求瓷砖不能超出矩阵、每个单元格只能被一张瓷砖覆盖、每张瓷砖可以旋转或翻转使用
思路:
题目给的数据很小,使用爆搜解决;
每次找到还没有放置瓷砖的单元格,尝试找到能够以该单元格为起点并能放得下(不超出矩阵范围、不和其他已放置瓷砖冲突)的瓷砖;
如果到最后能够让矩阵完全被覆盖,那么表明可以使用瓷砖铺满
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
class Main {
private static InputStream is = System.in;
private static final BufferedReader in = new BufferedReader(new InputStreamReader(is));
private static final PrintWriter out = new PrintWriter(System.out);
private static int h, w, n;
static void solve() throws IOException {
String[] ins = in.readLine().split(" ");
n = Integer.parseInt(ins[0]);
h = Integer.parseInt(ins[1]);
w = Integer.parseInt(ins[2]);
int[][] tiles = new int[n][2];
for (int i = 0; i < n; i ++) {
ins = in.readLine().split(" ");
tiles[i][0] = Integer.parseInt(ins[0]);
tiles[i][1] = Integer.parseInt(ins[1]);
}
boolean[][] vis = new boolean[h][w]; // 记录哪些单元格没有被覆盖
boolean[] used = new boolean[n]; // 记录哪些瓷砖已经被使用
if (dfs(vis, used, tiles)) {
out.println("Yes");
} else {
out.println("No");
}
}
// 判断 子矩阵[左上角(a,b) 右下角(c,d)] 能否放得下瓷砖
static boolean check(boolean[][] vis, int a, int b, int c, int d) {
if (c > h || d > w) return false;
for (int i = a; i < c; i ++) {
for (int j = b; j < d; j ++) {
if (vis[i][j]) return false;
}
}
return true;
}
static void set(boolean[][] vis, int a, int b, int c, int d, boolean val) {
for (int i = a; i < c; i ++) {
for (int j = b; j < d; j ++) {
vis[i][j] = val;
}
}
}
static boolean dfs(boolean[][] vis, boolean[] used, int[][] tiles) {
// 先找到没有放置的单元格
int x = -1, y = -1;
for (int i = 0; i < h; i ++) {
boolean ok = false;
for (int j = 0; j < w; j ++) {
if (!vis[i][j]) {
x = i;
y = j;
ok = true;
break;
}
}
if (ok) break;
}
// 如果没有找到,那么表明矩阵已经被完全覆盖
if (x == -1 && y == -1) {
return true;
}
// 查找没有使用过的瓷砖
for (int i = 0; i < n; i ++) {
if (!used[i]) {
int a = tiles[i][0], b = tiles[i][1];
// 判断是否能放置
if (check(vis, x, y, x + a, y + b)) {
used[i] = true;
set(vis, x, y, x + a, y + b, true);
if (dfs(vis, used, tiles)) {
return true;
}
// 恢复原状
set(vis, x, y, x + a, y + b, false);
used[i] = false;
}
// 如果瓷砖不是正方形,那么可以旋转后使用,也就是交换一下长和宽
if (a != b && check(vis, x, y, x + b, y + a)) {
used[i] = true;
set(vis, x, y, x + b, y + a, true);
if (dfs(vis, used, tiles)) {
return true;
}
set(vis, x, y, x + b, y + a, false);
used[i] = false;
}
}
}
return false;
}
public static void main(String[] args) throws IOException {
int t = 1;
// t = Integer.parseInt(in.readLine());
while (t -- > 0) {
solve();
}
in.close();
out.flush();
out.close();
}
}
E - Colorful Subsequence
题意:
给一行球,每一个球都有对应颜色和价值,现在要求移除 K 个球,使得移除后任意相邻两个球的颜色不能相同;
求移除K个球后剩余球的最大价值总和,如果无法满足任意相邻两个球颜色不同,输出 -1
一开始想的是DP,但是不会写;按照自己的思路写了个贪心:每段连续相同颜色的球都只能取一个最大价值的球,如果还剩余球,就逐一移除较小价值的球,最后发现这样处理完后的序列存在一种情况,两种颜色相互交替出现,如果之间存在一种较小价值的球,那么这个将会被移除,最后导致存在相邻两个球的颜色相同(这种写法WA了4个样例)
题解后面再补
2024/03/17补
状态定义:定义
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 为考虑前 i
个球、移除了 j
个球、剩余球(不包括未考虑的)最右边的是颜色 k
的最大价值总和(
0
≤
i
≤
N
,
0
≤
j
≤
K
,
0
≤
k
≤
N
0 \le i \le N, 0 \le j \le K, 0 \le k \le N
0≤i≤N,0≤j≤K,0≤k≤N)
- 如果移除了
j
个球,也不能保证剩余的球中任意相邻两个颜色不同,则值定义为-INF
- 如果
i < j
也将值定义为-INF
表示无效;
为了好表示不考虑任何球时其 k
值,在初始球的最左边加上颜色为 0
且 价值为 0
的球,那么最开始的状态
d
p
[
0
]
[
0
]
[
0
]
=
0
dp[0][0][0] = 0
dp[0][0][0]=0,而最终的答案应该是
max
1
≤
k
≤
N
(
d
p
[
N
]
[
K
]
[
k
]
)
\max_{1 \le k \le N}(dp[N][K][k])
max1≤k≤N(dp[N][K][k])
状态转移:对于第 i
个球:
- 如果颜色
C
i
≠
k
C_i \neq k
Ci=k,那么必须移除这个球(因为这个球不移除的话,最右边的球的颜色不是 k):
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
i
−
1
]
[
j
−
1
]
[
k
]
dp[i][j][k] = dp[i - 1][j - 1][k]
dp[i][j][k]=dp[i−1][j−1][k] 。(这个很好理解,就是前
i - 1
个球中已经移除了j - 1
个球,且最后面球的颜色为 k,那移除第i
个球,也就是移除了j
个球,并不会改变最后球的颜色; - 如果颜色
C
i
=
k
C_i = k
Ci=k,那么这个球 可以移除 或 不用移除:
- 移除这个球,就和上面的式子相同: d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 1 ] [ k ] dp[i][j][k] = dp[i - 1][j - 1][k] dp[i][j][k]=dp[i−1][j−1][k]
- 不移除这个球:
d
p
[
i
]
[
j
]
[
k
]
=
max
k
′
≠
k
&
&
1
≤
k
′
≤
N
(
d
p
[
i
−
1
]
[
j
]
[
k
′
]
)
dp[i][j][k]=\max_{k' \neq k \&\& 1\le k' \le N }{(dp[i - 1][j][k'])}
dp[i][j][k]=maxk′=k&&1≤k′≤N(dp[i−1][j][k′])。(不移除也就是
j
没有变化,但是前一个状态的最后球颜色不能和当前状态的相同)
基于上面的状态转移,总的时间复杂度为 O ( N 3 ⋅ K ) O(N^3 \cdot K) O(N3⋅K),很明显会超时,而且很难开到 N 2 ⋅ K N^2 \cdot K N2⋅K 大小的数组,
// 超时、超内存代码,仅过了6个样例
static void solve() throws IOException {
String[] ins = in.readLine().split(" ");
int N = Integer.parseInt(ins[0]), K = Integer.parseInt(ins[1]);
final long INF = Long.MAX_VALUE >> 1;
long[][][] dp = new long[N + 1][K + 1][N + 1];
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= K; j++) {
for (int k = 0; k <= N; k ++) {
dp[i][j][k] = -INF;
}
}
}
dp[0][0][0] = 0;
for (int i = 1; i <= N; i ++) {
ins = in.readLine().split(" ");
int color = Integer.parseInt(ins[0]), value = Integer.parseInt(ins[1]);
for (int j = 0; j <= K; j ++) {
if (j > i) break;
for (int k = 0; k <= N; k ++) {
if (j > 0) {
dp[i][j][k] = dp[i - 1][j - 1][k];
}
if (color == k) {
for (int k1 = 0; k1 <= N; k1 ++) {
if (k1 == k) continue;
dp[i][j][k] = Math.max(dp[i][j][k], dp[i - 1][j][k1] + value);
}
}
}
}
}
long ans = -INF;
for (int i = 0; i <= N; i++) {
ans = Math.max(ans, dp[N][K][i]);
}
out.println(ans < 0 ? -1 : ans);
}
上面的代码中遍历颜色 k
的操作时间复杂度为
O
(
N
⋅
K
)
O(N \cdot K)
O(N⋅K)
能否优化这个操作呢?
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 要取最大值有两种方式,第一种是移除;第二种是不移除(也就是最深层的循环),在第二种方式的操作中,如果取到了最大值,第二种方式中的 k1
对应的状态
d
p
[
i
−
1
]
[
j
]
[
k
1
]
dp[i - 1][j][k_1]
dp[i−1][j][k1] 也应该是最大值,那我们是不是可以通过仅维护最大值和次最大值的方式来优化这个操作呢?(为了保证存在不同的颜色可选,那么需要额外维护一个次最大值,也可能是不同颜色的两个最大值)
考虑如何维护最大值:
- 当
j
=
0
j = 0
j=0 时,不移除任何球,那么剩余序列的最右球的颜色为
C
i
C_i
Ci
- 如果
k
≠
C
i
k \neq C_i
k=Ci, 那么
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 的值为
-INF
(次大值) - 如果 k = C i k = C_i k=Ci,那么 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 的值为 d p [ i − 1 ] [ 0 ] [ k ] + V i dp[i - 1][0][k] + V_i dp[i−1][0][k]+Vi(最大值)
- 如果
k
≠
C
i
k \neq C_i
k=Ci, 那么
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 的值为
- 当
j
≠
0
j \neq 0
j=0 时,假设最大值和次大值的颜色分别为
k
1
(
i
−
1
,
j
−
1
)
k_1(i - 1, j - 1)
k1(i−1,j−1) 和
k
2
(
i
−
1
,
j
−
1
)
k_2(i - 1, j - 1)
k2(i−1,j−1):
- 当 k ≠ C i ∧ k ≠ k 1 ∧ k ≠ k 2 k \neq C_i \wedge k \neq k_1 \wedge k \neq k_2 k=Ci∧k=k1∧k=k2,有 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 1 ] [ k ] ≤ d p [ i − 1 ] [ j − 1 ] [ k 2 ] dp[i][j][k] = dp[i - 1][j - 1][k] \le dp[i - 1][j - 1][k_2] dp[i][j][k]=dp[i−1][j−1][k]≤dp[i−1][j−1][k2](颜色和 k 1 、 k 2 k_1、k_2 k1、k2不一样,也就是说当前状态不能从 k 1 、 k 2 k_1、k_2 k1、k2 对应的状态转移过来,值必然 小于等于 最大值和次大值)
- 当 k = k 1 ∨ k = k 2 k = k_1 \vee k =k_2 k=k1∨k=k2,有 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 1 ] [ k 1 ] + V i ≥ d p [ i − 1 ] [ j − 1 ] [ k 1 ] ≥ d p [ i − 1 ] [ j − 1 ] [ k 2 ] dp[i][j][k] = dp[i - 1][j - 1][k_1] + V_i \ge dp[i - 1][j - 1][k_1] \ge dp[i - 1][j - 1][k_2] dp[i][j][k]=dp[i−1][j−1][k1]+Vi≥dp[i−1][j−1][k1]≥dp[i−1][j−1][k2],这里 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 的最大值取决于 k = C i , k = k 1 , k = k 2 k = C_i, k = k_1, k = k_2 k=Ci,k=k1,k=k2
给上面背包加上一位,用于维护最大值和次大值
第一维考虑前 i
个背包这里可以省略,因为不需要维护更多的状态,只需要维护上一个 i - 1
状态即可;