AtCoder Beginner Contest 321

321-like Checker

模拟。

1
2
3
4
5
6
7
8
9
10
11
public static void solve() {
int n = io.nextInt();
String s = String.valueOf(n);
for (int i = 0; i < s.length() - 1; i++) {
if (s.charAt(i) <= s.charAt(i + 1)) {
io.println("No");
return;
}
}
io.println("Yes");
}

Cutoff

比赛时是暴力做的,赛后这个 \(O(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(), x = io.nextInt();
int min = 101, max = -1, sum = 0;
for (int i = 0; i < n - 1; i++) {
int t = io.nextInt();
sum += t;
min = Math.min(min, t);
max = Math.max(max, t);
}
int t = x - (sum - min - max);
if (t > max) {
io.println(-1);
} else {
io.println(t <= min ? 0 : t);
}
}

321-like Searcher

原来是使用十个二进制位来表示对应数字是否存在,通过暴力枚举算出所有可能的数,最后排序获取对应的位置即可,真的没想到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void solve() {
int k = io.nextInt();
List<Long> ans = new ArrayList<>();
for (int i = 2; i < 1 << 10; i++) {
long x = 0L;
for (int j = 9; j >= 0; j--) {
if ((i >> j & 1) == 1) {
x = x * 10 + j;
}
}
ans.add(x);
}
Collections.sort(ans);
io.println(ans.get(k - 1));
}

Set Menu

二分,状态真差,把加法和乘法混淆了。

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
public static void solve() {
int n = io.nextInt(), m = io.nextInt(), p = io.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = io.nextInt();
}
int[] b = new int[m];
for (int i = 0; i < m; i++) {
b[i] = io.nextInt();
}
Arrays.sort(b);
long[] sum = new long[m + 1];
for (int i = 0; i < m; i++) {
sum[i + 1] = sum[i] + b[i];
}
long ans = 0L;
for (int i = 0; i < n; i++) {
int x = p - a[i];
int lo = 0, hi = m - 1;
while (lo <= hi) {
int mid= lo + (hi - lo) / 2;
if (b[mid] <= x) lo = mid + 1;
else hi = mid - 1;
}
ans += ((long) lo * a[i] + sum[lo]) + (long) (m - lo) * p;
}
io.println(ans);
}

第 363 场力扣周赛

计算 K 置位下标对应元素的和

模拟。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int sumIndicesWithKSetBits(List<Integer> nums, int k) {
int n = nums.size(), ans = 0;
for (int i = 0; i < n; i++) {
if (Integer.bitCount(i) == k) {
ans += nums.get(i);
}
}
return ans;
}
}

让所有学生保持开心的分组方法数

比赛时又写复杂了,当时是想到所有相同的数都必须同时选,所以加了一层循环来跳过相同的数。但是相同的数天然的不满足判断条件,所以不需要这样写。这题唯一需要注意的就是特判全都不选的情况,以及全都选的情况必定满足,可以直接加到答案里(以减少判断代码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int countWays(List<Integer> nums) {
Collections.sort(nums);
int n = nums.size(), ans = 1;
if (nums.get(0) > 0) ans++;
for (int i = 0; i < n - 1; i++) {
if (i + 1 > nums.get(i) && i + 1 < nums.get(i + 1)) {
ans++;
}
}
return ans;
}
}

最大合金数

比赛时又又写复杂了,当时是把所有的库存都清除了再二分的,其实可以直接二分!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int maxNumberOfAlloys(int n, int k, int budget, List<List<Integer>> composition, List<Integer> stock, List<Integer> cost) {
int ans = 0;
for (var cur : composition) {
int lo = 0, hi = (int) 2e8;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
long cnt = 0L;
for (int i = 0; i < n; i++) {
cnt += Math.max((long) cur.get(i) * mid - stock.get(i), 0L) * cost.get(i);
}
if (cnt <= budget) lo = mid + 1;
else hi = mid - 1;
}
ans = Math.max(ans, hi);
}
return ans;
}
}

完全子集的最大元素和

注意题目的描述是每对元素的乘积都是完全平方数。朴素的想法就是对下标进行质因数分解,将所有出现次数为奇数质因数相乘,其结果作为桶的下标,把所有同类的数放在同一个桶里面,然后对每个桶求和取最大值,这样的时间复杂度是 \(O(n\sqrt{n})\)。但是有 \(O(n)\) 的解法,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public long maximumSum(List<Integer> nums) {
long ans = 0L;
int n = nums.size();
for (int i = 1; i <= n; i++) {
long sum = 0L;
for (int j = 1; i * j * j <= n; j++) {
sum += nums.get(i * j * j - 1);
}
ans = Math.max(ans, sum);
}
return ans;
}
}

第 113 场力扣夜喵双周赛

使数组成为递增数组的最少右移次数

直接从最小值开始判断数组是否递增,或者可以找到第一个递减的位置,然后再判断数组是否递增(因为如果数组满足条件,则其最多只有一个递减段)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int minimumRightShifts(List<Integer> nums) {
int n = nums.size(), idx = -1, min = 101;
for (int i = 0; i < n; i++) {
if (nums.get(i) < min) {
min = nums.get(i);
idx = i;
}
}
for (int i = 0; i < n - 1; i++) {
int x = (idx + i) % n, y = (x + 1) % n;
if (nums.get(x) > nums.get(y)) return -1;
}
return (n - idx) % n;
}
}

删除数对后的最小数组长度

贪心,比赛时我是用双指针做的,前半部分和后半部分进行匹配(当时边界想了很久,真笨!)。其他做法,参考题解:【小羊肖恩】数学 + 贪心:解决较长数组脑筋急转弯问题的关键。(因为 HashMap 很慢,所以用双指针会更快。)

方法一:贪心

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int minLengthAfterRemovals(List<Integer> nums) {
int n = nums.size(), i = 0;
for (int j = (n + 1) / 2; j < n; j++) {
if (nums.get(i) < nums.get(j)) {
i++;
}
}
return n - i * 2;
}
}

方法二:贪心 + 数学

1
2
3
4
5
6
7
8
9
10
class Solution {
public int minLengthAfterRemovals(List<Integer> nums) {
int n = nums.size(), max = 0;
Map<Integer, Integer> map = new HashMap<>();
for (int x : nums) {
max = Math.max(max, map.merge(x, 1, Integer::sum));
}
return 2 * max <= n ? n % 2 : n - (n - max) * 2;
}
}

统计距离为 k 的点对

枚举 \(x_{1}\oplus x_{2}\) 的值为 \(p\),可以得到 \(y_{1}\oplus y_{2}\) 的值为 \(k-p\)。可以使用 HashMap 对前缀中的值计数来求解,需要注意循环的顺序,如果调换顺序会使代码变复杂,会花费更多的时间计算答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int countPairs(List<List<Integer>> coordinates, int k) {
int ans = 0;
Map<List<Integer>, Integer> map = new HashMap<>();
for (var c : coordinates) {
int x = c.get(0), y = c.get(1);
for (int i = 0; i <= k; i++) {
ans += map.getOrDefault(List.of(x ^ i, y ^ (k - i)), 0);
}
map.merge(c, 1, Integer::sum);
}
return ans;
}
}

可以到达每一个节点的最少边反转次数

换根 DP,关键是要想到建立反向边,并为边添加相应的边权。

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
class Solution {
public int[] minEdgeReversals(int n, int[][] edges) {
List<int[]>[] g = new List[n];
Arrays.setAll(g, k -> new ArrayList<>());
for (var e : edges) {
int u = e[0], v = e[1];
g[u].add(new int[]{v, 0});
g[v].add(new int[]{u, 1});
}
int[] ans = new int[n];
ans[0] = dfs(0, -1, g);
dfs2(0, -1, g, ans);
return ans;
}

private int dfs(int x, int fa, List<int[]>[] g) {
int res = 0;
for (int t[] : g[x]) {
int y = t[0], w = t[1];
if (y == fa) continue;
res += dfs(y, x, g) + w;
}
return res;
}

private void dfs2(int x, int fa, List<int[]>[] g, int[] ans) {
for (int t[] : g[x]) {
int y = t[0], w = t[1];
if (y == fa) continue;
ans[y] = ans[x] + (w == 0 ? 1 : -1);
dfs2(y, x, g, ans);
}
}
}