第 365 场力扣周赛

有序三元组中的最大值 I

同下。

有序三元组中的最大值 II

方法一:枚举 j

比赛时第一想法是枚举 \(j\),然后取左边和右边的最大值计算答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public long maximumTripletValue(int[] nums) {
int n = nums.length;
int[] pre = new int[n + 1];
int[] suf = new int[n + 1];
for (int i = 0; i < n; i++) {
pre[i + 1] = Math.max(pre[i], nums[i]);
}
for (int i = n - 1; i >= 0; i--) {
suf[i] = Math.max(suf[i + 1], nums[i]);
}
long ans = 0L;
for (int j = 1; j < n - 1; j++) {
ans = Math.max(ans, (long) (pre[j] - nums[j]) * suf[j + 1]);
}
return ans;
}
}

方法二:枚举 k

参考灵神的题解,可以枚举 \(k\),使空间复杂度降为 \(O(1)\)。主要想法就是枚举时,维护前缀最大差值。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public long maximumTripletValue(int[] nums) {
long ans = 0;
int maxDiff = 0, preMax = 0;
for (int x : nums) {
ans = Math.max(ans, (long) maxDiff * x);
maxDiff = Math.max(maxDiff, preMax - x);
preMax = Math.max(preMax, x);
}
return ans;
}
}

无限数组的最短子数组

比赛时思路很快出来,但是实现的时候漏掉一些边界条件,导致 WA 多次,本来有机会进第一页的。比较直接的想法是枚举起始位置,然后利用前缀和二分结束的位置,如果满足条件就记入答案。也可以使用哈希表来优化,避免二分。这里贴一下灵神的滑动窗口解法,时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int minSizeSubarray(int[] nums, int target) {
int n = nums.length;
long sum = 0L, pre = 0L;
for (int x : nums) sum += x;
int q = (int) (target / sum), r = (int) (target % sum);
int lo = 0, hi = 0, ans = Integer.MAX_VALUE;
while (hi < 2 * n) {
pre += nums[hi++ % n];
while (pre > r) {
pre -= nums[lo++ % n];
}
if (pre == r) {
ans = Math.min(ans, hi - lo);
}
}
return ans == Integer.MAX_VALUE ? -1 : ans + q * n;
}
}

有向图访问计数

看完题目就知道是内向基环树,直接修改上次的代码,轻松通过,本题是上次的简化版。

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
52
53
class Solution {
public int[] countVisitedNodes(List<Integer> edges) {
int n = edges.size();
// 建立环外节点的反向边
int[] in = new int[n];
List<Integer>[] reverse = new List[n];
Arrays.setAll(reverse, r -> new ArrayList<>());
for (int i = 0; i < n; i++) {
in[edges.get(i)]++;
reverse[edges.get(i)].add(i);
}
// 拓扑序去除环外节点
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (in[i] == 0) q.add(i);
}
while (!q.isEmpty()) {
int x = q.poll();
if (--in[edges.get(x)] == 0) {
q.offer(edges.get(x));
}
}
// 记录每个节点在哪个环
int[] cirNum = new int[n];
boolean[] vis = new boolean[n];
List<Integer> circles = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (!vis[i] && in[i] != 0) {
int cnt = 0;
for (int cur = i; !vis[cur]; cur = edges.get(cur)) {
vis[cur] = true;
cirNum[cur] = circles.size();
cnt++;
}
circles.add(cnt);
}
}
// 对环内的每个节点向环外进行 dfs
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
if (in[i] != 0) dfs(i, reverse, in, circles.get(cirNum[i]), ans);
}
return ans;
}

private void dfs(int x, List<Integer>[] reverse, int[] in, int len, int[] ans) {
ans[x] = len;
for (int y : reverse[x]) {
if (in[y] != 0) continue;
dfs(y, reverse, in, len + 1, ans);
}
}
}

有个非常简单的写法,参考题解

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
class Solution {
public int[] countVisitedNodes(List<Integer> edges) {
int n = edges.size();
int[] ans = new int[n];
int[] vis = new int[n];
for (int i = 0; i < n; i++) {
if (ans[i] == 0) {
int cnt = 0, cirLen = 0, totLen = 0, pos = i;
while (vis[pos] == 0) {
vis[pos] = ++cnt;
pos = edges.get(pos);
}
if (ans[pos] == 0) {
cirLen = cnt - vis[pos] + 1;
totLen = cnt;
} else {
totLen = cnt + ans[pos];
}
pos = i;
while (ans[pos] == 0) {
ans[pos] = Math.max(totLen--, cirLen);
pos = edges.get(pos);
}
}
}
return ans;
}
}

Codeforces Round 901 (Div. 2)

Jellyfish and Undertale

每个工具对答案的贡献为 \(\min(a-1,x_{i})\),并且答案可能会爆 \(long\),状态好差,WA 两次。

1
2
3
4
5
6
7
8
public static void solve() {
int a = io.nextInt(), b = io.nextInt(), n = io.nextInt();
long ans = b;
for (int i = 0; i < n; i++) {
ans += Math.min(a - 1, io.nextInt());
}
io.println(ans);
}

Jellyfish and Game

数学题。当 \(k\bmod 2=1\) 时,先手会得到 \(\min(0,maxb-mina)\);反之,后手此时必然有最小价值的苹果 \(\min(mina,minb)\),而先手此时会有最大价值的苹果 \(\max(maxa,maxb)\),做一次交换即可。

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
public static void solve() {
int n = io.nextInt(), m = io.nextInt(), k = io.nextInt();

long suma = 0L;
int mina = Integer.MAX_VALUE, maxa = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
int x = io.nextInt();
suma += x;
mina = Math.min(mina, x);
maxa = Math.max(maxa, x);
}

int minb = Integer.MAX_VALUE, maxb = Integer.MIN_VALUE;
for (int i = 0; i < m; i++) {
int x = io.nextInt();
minb = Math.min(minb, x);
maxb = Math.max(maxb, x);
}

if (k % 2 == 1) {
io.println(suma + Math.max(0, maxb - mina));
} else {
io.println(suma + Math.max(0, maxb - mina) - Math.max(maxa, maxb) + Math.min(mina, minb));
}
}

Jellyfish and Green Apple

完了,官方解法看不懂,下面是我的解法,就是一直乘二求余,贪心的拆分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void solve() {
int n = io.nextInt(), m = io.nextInt();

int x = n % m, y = m / gcd(n, m);
if ((y & (y - 1)) != 0) {
io.println(-1);
return;
}

long ans = 0L;
while (x != 0) {
ans += x;
x *= 2;
x %= m;
}
io.println(ans);
}

private static int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}

下面是官解,大概懂了。\(\frac{a}{b}\) 表示最终每个人得到的苹果价值的小数部分(可以表示为 \(\sum_{i\in S}\frac{1}{2^{i}}\)),因为 \(b\) 必须是二的幂,所以 \(a\) 中 \(1\) 的个数就表示这个人所需的最小苹果片数(就是集合 \(S\) 中元素的个数),乘以 \(m\) 就表示最后总共的苹果片数,然后减去最开始的 \(n\) 片就是需要进行操作的次数(因为每次操作会增加 \(1\) 片)。(这个题解很详细,提供了另一种视角)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void solve() {
int n = io.nextInt(), m = io.nextInt();

n %= m;
int x = gcd(n, m), a = n / x, b = m / x;
if ((b & (b - 1)) != 0) {
io.println(-1);
} else {
io.println((long) Integer.bitCount(a) * m - n);
}
}

private static int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}

Jellyfish and Mex

其实是很简单的动态规划,但就是动不起来啊。定义 \(dp[i]\) 为将 \(MEX\) 变为 \(i\) 需要的最小代价,然后从大到小转移即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final int N = 5001;

public static void solve() {
int n = io.nextInt();
int[] cnt = new int[N];
for (int i = 0; i < n; i++) {
int x = io.nextInt();
if (x < n) cnt[x]++;
}

int mex = 0;
while (cnt[mex] > 0) mex++;
long[] dp = new long[mex + 1];
Arrays.fill(dp, Long.MAX_VALUE);
dp[mex] = 0;
for (int i = mex - 1; i >= 0; i--) {
for (int j = i + 1; j <= mex; j++) {
dp[i] = Math.min(dp[i], dp[j] + (long) (cnt[i] - 1) * j + i);
}
}
io.println(dp[0]);
}

AtCoder Beginner Contest 322

First ABC 2

1
2
3
4
5
6
public static void solve() {
int n = io.nextInt();
String s = io.next();
int ans = s.indexOf("ABC");
io.println(ans < 0 ? -1 : ans + 1);
}

Prefix and Suffix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void solve() {
int n = io.nextInt(), m = io.nextInt();
String s = io.next(), t = io.next();
int ans = 0;
for (int i = 0; i < n; i++) {
if (s.charAt(i) != t.charAt(i)) {
ans |= 2;
}
if (s.charAt(i) != t.charAt(m - n + i)) {
ans |= 1;
}
}
io.println(ans);
}

Festival

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

Polyomino

模拟题,使用位运算似乎更简单,题解

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public static void solve() {
char[][][] G = new char[3][4][4];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
G[i][j] = io.next().toCharArray();
}
}
io.println(dfs(0, G, new char[4][4]) ? "Yes" : "No");
}

private static boolean dfs(int i, char[][][] G, char[][] C) {
if (C == null) return false;
if (i == 3) return check(C);
for (int j = 0; j < 4; j++) {
for (int dx = -3; dx <= 3; dx++) {
for (int dy = -3; dy <= 3; dy++) {
char[][] T = move(dx, dy, G[i]);
if (T == null) continue;
if (dfs(i + 1, G, add(T, C))) return true;
}
}
G[i] = rotate(G[i]);
}
return false;
}

private static char[][] rotate(char[][] A) {
char[][] B = new char[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
B[i][j] = A[3 - j][i];
}
}
return B;
}

private static char[][] move(int dx, int dy, char[][] G) {
char[][] res = new char[4][4];
for (int i = 0; i < 4; i++) {
Arrays.fill(res[i], '.');
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int nx = i + dx, ny = j + dy;
if(G[i][j] == '#') {
if (nx < 0 || nx >= 4 || ny < 0 || ny >= 4) {
return null;
}
res[nx][ny] = G[i][j];
}
}
}
return res;
}

private static char[][] add(char[][] T, char[][] C) {
char[][] res = new char[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (C[i][j] == '#' && T[i][j] == '#') return null;
if (C[i][j] == '#' || T[i][j] == '#') res[i][j] = '#';
else res[i][j] = '.';
}
}
return res;
}

private static boolean check(char[][] C) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (C[i][j] != '#') return false;
}
}
return true;
}

Product Development

动态规划,\(dp[i][j]\) 表示从前 \(i\) 个计划中选择开发计划,使得参数到达 \(j\),需要的最小成本,其中 \(j\) 表示 \(a_{1},a_{2},\dots,a_{k}\) 的某个取值,通过将序列看作 \(p+1\) 进制数,可以将一个序列转换为某个数值(在这里就是 \(j\))。

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
public static void solve() {
int n = io.nextInt(), k = io.nextInt(), p = io.nextInt();

int[] pw = new int[k + 1];
pw[0] = 1;
for (int i = 1; i <= k; i++) {
pw[i] = pw[i - 1] * (p + 1);
}

long[] dp = new long[pw[k]];
Arrays.fill(dp, -1L);
dp[0] = 0;

for (int i = 0; i < n; i++) {
int c = io.nextInt();
int[] a = new int[k];
for (int j = 0; j < k; j++) {
a[j] = io.nextInt();
}

for (int s = pw[k] - 1; s >= 0; s--) {
int t = 0;
for (int j = 0; j < k; j++) {
int cur = s / pw[j] % (p + 1);
int nxt = Math.min(p, cur + a[j]);
t += nxt * pw[j];
}
if (dp[s] != -1 && (dp[t] == -1 || dp[t] > dp[s] + c)) {
dp[t] = dp[s] + c;
}
}
}
io.println(dp[pw[k] - 1]);
}