Codeforces Round 896 (Div. 2)

Make It Zero

挺简单的一道题,偶数长度的数组操作两次就可以,如果是奇数长度,则额外操作两次。写的时候,把 \(n\) 错写成 \(n-1\),找 BUG 花了一倍的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void solve() {
int n = io.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
}
if (n % 2 == 0) {
io.println(2);
io.println(1 + " " + n);
io.println(1 + " " + n);
return;
}
io.println(4);
io.println(1 + " " + (n - 1));
io.println(1 + " " + (n - 1));
io.println((n - 1) + " " + n);
io.println((n - 1) + " " + n);
}

2D Traveling

\(a\) 和 \(b\) 的最短距离有两种情况,一个是 \(a\) 和 \(b\) 的曼哈顿距离,另一个是 \(a\) 和 \(b\) 经过 \(k\) 的曼哈顿距离,该情况只要求 \(k\) 个点中距离 \(a\) 和距离 \(b\) 最近的距离就行。比赛时遇到个坑点,两个 Long.MAX_VALUE 相加会溢出,所以初始时除以二。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void solve() {
int n = io.nextInt(), k = io.nextInt(), a = io.nextInt(), b = io.nextInt();
long[] x = new long[n], y = new long[n];
for (int i = 0; i < n; i++) {
x[i] = io.nextInt();
y[i] = io.nextInt();
}
long ak = Long.MAX_VALUE / 2, bk = Long.MAX_VALUE / 2;
for (int i = 0; i < k; i++) {
ak = Math.min(ak, Math.abs(x[i] - x[a - 1]) + Math.abs(y[i] - y[a - 1]));
bk = Math.min(bk, Math.abs(x[i] - x[b - 1]) + Math.abs(y[i] - y[b - 1]));
}
long ab = Math.abs(x[b - 1] - x[a - 1]) + Math.abs(y[b - 1] - y[a - 1]);
io.println(Math.min(ab, ak + bk));
}

Fill in the Matrix

比赛时代码很乱,赛后总是可以优化成比较简单的形式。分类讨论,\(n\) 和 \(m\) 的大小关系,可以直接得出最大美丽值,需要注意特判 \(m=1\) 的情况。然后就是构造,当 \(n\leq m-1\) 时,让一个从 \(0\) 开始的数组循环左移来构造行,这样可以保证得到最大美丽值;当 \(n>m-1\) 时,前 \(m-1\) 行与之前一样构造,之后多余的行只需要和最后一行相同即可(保证不会影响美丽值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void solve() {
int n = io.nextInt(), m = io.nextInt();
if (m == 1) {
io.println(0);
} else if (n <= m - 1) {
io.println(n + 1);
} else {
io.println(m);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (i < Math.min(n, m - 1)) {
io.print((j + i) % m + " ");
} else {
io.print(j + " ");
}
}
io.println();
}
}

Candy Party (Easy Version)

找 BUG 花了半个小时,将判断 hi 是否是二的幂写成 hi % 2 != 0,修改为 Long.bitCount(hi) != 1 后通过,也可以写成 (hi & hi - 1) != 0。因为每个人都需要发送和接收糖果,计算每个人和平均糖果数的差值 \(x\),如果 \(x\) 的二进制位不是由连续的 \(1\) 组成,那么就无解,否则总是有唯一的 \(lo\) 和 \(hi\)(都是二的幂),使得 \(hi-lo=|x|\)。这样可以计算出每个人发送和接收多少糖果,如果最后相互抵消,则存在满足题目要求的交换方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void solve() {
int n = io.nextInt();
long sum = 0L;
long[] a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
sum += a[i];
}
if (sum % n != 0) {
io.println("NO");
return;
}
long avg = sum / n;
int[] cnt = new int[32];
for (int i = 0; i < n; i++) {
if (a[i] == avg) continue;
long x = Math.abs(a[i] - avg);
long lo = x & -x, hi = x + lo;
if (Long.bitCount(hi) != 1) {
io.println("NO");
return;
}
int p = Long.numberOfTrailingZeros(lo);
int q = Long.numberOfTrailingZeros(hi);
if (a[i] > avg) {
cnt[p]--;
cnt[q]++;
} else {
cnt[q]--;
cnt[p]++;
}
}
for (int i = 0; i < 32; i++) {
if (cnt[i] != 0) {
io.println("NO");
return;
}
}
io.println("YES");
}

Candy Party (Hard Version)

考虑什么人可以不发送或者不接收糖果,必定是持有糖果数与平均糖果数的差值为二的幂的人,它们比原来多出一种选择,就是只执行一次发送或接收。具体操作见代码,有点说不清。最后大概是从高位到低位遍历,如果当前位不满足条件,就将低一位的差值与平均糖果数为二的幂的数分解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public static void solve() {
int n = io.nextInt();
long sum = 0L;
long[] a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
sum += a[i];
}
if (sum % n != 0) {
io.println("NO");
return;
}
long avg = sum / n;
int[] cnt = new int[32];
int[] in = new int[32], out = new int[32];
for (int i = 0; i < n; i++) {
if (a[i] == avg) continue;
long x = Math.abs(a[i] - avg);
if ((x & x - 1) == 0) {
int r = Long.numberOfTrailingZeros(x);
if (a[i] < avg) in[r]++;
else out[r]++;
continue;
}
long lo = x & -x, hi = x + lo;
if ((hi & hi - 1) != 0) {
io.println("NO");
return;
}
int p = Long.numberOfTrailingZeros(lo);
int q = Long.numberOfTrailingZeros(hi);
if (a[i] > avg) {
cnt[p]--;
cnt[q]++;
} else {
cnt[q]--;
cnt[p]++;
}
}
for (int i = 31; i >= 0; i--) {
cnt[i] += out[i] - in[i];
if (i == 0) break;
in[i - 1] -= cnt[i];
out[i - 1] += cnt[i];
if (in[i - 1] < 0 || out[i - 1] < 0) {
io.println("NO");
return;
}
}
io.println(cnt[0] == 0 ? "YES" : "NO");
}

Educational Codeforces Round 154 (Rated for Div. 2)

Prime Deletion

从 \(1\) 到 \(9\) 的序列中删除一些数(至少保留两位),使得结果为质数。可以发现 \(13\) 和 \(31\) 都是质数,所以判断 \(1\) 和 \(3\) 的先后顺序,然后输出即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void solve() {
char[] s = io.next().toCharArray();
int n = s.length;
for (int i = 0; i < n; i++) {
if (s[i] == '1') {
io.println(13);
return;
} else if(s[i] == '3') {
io.println(31);
return;
}
}
}

Two Binary Strings

比赛时我是从左往右遍历记录不相等的数量,如果有不相等的,那么就需要一个 \(0\),否则遇到 \(1\) 就输出 YES。和正解的思路是一样的,就是麻烦一点。正解是有相同的 \(01\) 出现时就输出 YES。

1
2
3
4
5
6
7
8
9
10
11
12
public static void solve() {
char[] a = io.next().toCharArray();
char[] b = io.next().toCharArray();
int n = a.length;
for (int i = 0; i < n - 1; i++) {
if (a[i] == b[i] && a[i] == '0' && a[i + 1] == b[i + 1] && a[i + 1] == '1') {
io.println("YES");
return;
}
}
io.println("NO");
}

Queries for the Array

比较简单的写法就是用一个标记数组做记录,递增会向左传递,递减会向右传递,然后判断是否冲突即可。更进一步观察,可以发现只需要记录最大的递增位置,和最小的递减位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void solve() {
char[] s = io.next().toCharArray();
int n = s.length;
boolean ok = true;
int pos = -1, neg = n, cur = -1;
for (char c : s) {
if (c == '+') {
cur++;
} else if (c == '-') {
if (cur-- == neg) {
neg = n;
}
pos = Math.min(pos, cur);
} else if (c == '0') {
if (cur == pos || cur <= 0) {
ok = false;
break;
}
neg = Math.min(neg, cur);
} else {
if (neg <= cur) {
ok = false;
break;
}
pos = cur;
}
}
io.println(ok ? "YES" : "NO");
}

Sorting By Multiplication

没想到啊。枚举负数前缀的长度:在负数前缀中,如果 \(a[i]<=a[i+1]\),就需要操作一次;在正数后缀中,如果 \(a[i]>=a[i+1]\) 就需要操作一次。(下面的代码很妙啊,不需要加额外的判断语句。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void solve() {
int n = io.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
}
int cnt = 0;
for (int i = 0; i < n - 1; i++) {
if (a[i] >= a[i + 1]) {
cnt++;
}
}
int ans = cnt;
for (int i = 1; i < n; i++) {
if (a[i - 1] >= a[i]) cnt--;
ans = Math.min(ans, cnt + 1);
if (a[i - 1] <= a[i]) cnt++;
}
io.println(ans);
}

Pinely Round 2 (Div. 1 + Div. 2)

Channel

如果同时在线人数到达 \(n\),就表示所有人都阅读过;否则,如果总上线人数大于等于 \(n\),则有可能所有人阅读过;否则,不可能所有人阅读过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void solve() {
int n = io.nextInt(), a = io.nextInt(), q = io.nextInt();
String s = io.next();
int cur = a, tot = a;
for (int i = 0; i < q && cur < n; i++) {
if (s.charAt(i) == '+') {
cur++;
tot++;
} else {
cur--;
}
}
if (cur == n) {
io.println("YES");
} else if (tot >= n) {
io.println("MAYBE");
} else {
io.println("NO");
}
}

Split Sort

对于每个 \(p_{i}=k+1\) 和 \(p_{j}=k\) 并且 \(i<j\),那么就一定要选一次 \(x=k+1\)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void solve() {
int n = io.nextInt();
int[] p = new int[n];
for (int i = 0; i < n; i++) {
p[i] = io.nextInt() - 1;
}
int[] map = new int[n];
for (int i = 0; i < n; i++) {
map[p[i]] = i;
}
int ans = 0;
for (int i = 1; i < n; i++) {
if (map[i] < map[i - 1]) {
ans++;
}
}
io.println(ans);
}

MEX Repetition

每执行一次操作,就会去除最后一个数,并将 \(MEX\) 添加到序列头部。所以可以通过在数组末尾加上原始数组的 \(MEX\),将操作看成是向左移动循环数组的起始索引。求原始数组的 \(MEX\) 可以使用求和公式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void solve() {
int n = io.nextInt(), k = io.nextInt();
long sum = 0L;
int[] a = new int[n + 1];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
sum += a[i];
}
a[n] = (int) ((long) (1 + n) * n / 2 - sum);
k = k % (n + 1);
for (int i = 0; i < n; i++) {
io.print(a[(-k + n + 1 + i) % (n + 1)] + " ");
}
io.println();
}

Two-Colored Dominoes

横放的牌只会对列有影响,竖放的牌只会对行有影响,所以分别处理。按行遍历竖放的牌,每当遇到 \(U\) 就染上和上次相反的颜色,如果该行只包含奇数个 \(U\),就返回 \(-1\)。横放的牌同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void solve() {
int n = io.nextInt(), m = io.nextInt();
char[][] s = new char[n][];
for (int i = 0; i < n; i++) {
s[i] = io.next().toCharArray();
}
final char[] aux = {'B', 'W'};
for (int i = 0; i < n - 1; i++) {
int xor = 0;
for (int j = 0; j < m; j++) {
if (s[i][j] == 'U') {
s[i][j] = aux[xor];
s[i + 1][j] = aux[xor ^ 1];
xor ^= 1;
}
}
if (xor != 0) {
io.println(-1);
return;
}
}
for (int j = 0; j < m - 1; j++) {
int xor = 0;
for (int i = 0; i < n; i++) {
if (s[i][j] == 'L') {
s[i][j] = aux[xor];
s[i][j + 1] = aux[xor ^ 1];
xor ^= 1;
}
}
if (xor != 0) {
io.println(-1);
return;
}
}
for (int i = 0; i < n; i++) {
io.println(new String(s[i]));
}
}

Speedrun

其实思路是知道的,就是不知道怎么写。这个解法看着有点懵,可能其他解法会更好理解一点。注意题目给定 \(a_{i}<b_{i}\)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void solve() {
int n = io.nextInt(), m = io.nextInt(), k = io.nextInt();
int[] h = new int[n];
for (int i = 0; i < n; i++) {
h[i] = io.nextInt();
}
List<Integer>[] g = new List[n];
Arrays.setAll(g, idx -> new ArrayList<>());
for (int i = 0; i < m; i++) {
int a = io.nextInt() - 1, b = io.nextInt() - 1;
g[a].add(b);
}
// dp[i] 表示完成所有依赖第 i 个任务的任务需要的时间(从 h[i] 开始)
long[] dp = new long[n];
for (int i = n - 1; i >= 0; i--) {
for (int j : g[i]) {
dp[i] = Math.max(dp[i], dp[j] + (h[j] - h[i] + k) % k);
}
}
// dp[i] 表示完成所有依赖第 i 个任务的任务需要的时间(从零开始)
long max = 0L;
for (int i = 0; i < n; i++) {
dp[i] += h[i];
max = Math.max(max, dp[i]);
}
// 按照 h[i] 的大小,从小到大枚举起点
Integer[] aux = new Integer[n];
for (int i = 0; i < n; i++) {
aux[i] = i;
}
Arrays.sort(aux, (i, j) -> h[i] - h[j]);
long ans = Long.MAX_VALUE;
for (int i : aux) {
ans = Math.min(ans, max - h[i]);
// 如果起点大于 h[i],那么任务 i 的完成时间需要加 k,从而导致 dp[i] + k
// 其实只要枚举入度为 0 的任务就行,但是即使任务 i 不是入度为 0 任务也没有关系,因为对答案没有影响
max = Math.max(max, dp[i] + k);
}
io.println(ans);
}