D - JoJo's Incredible Adventures
大致题义:
有一串由 0,1 构成的字符串,每次循环右移一位,行编号从 0 一直到 n-1。求这些行里由 1 构成的最大矩形面积。
题解:
我们其实可以观察到一串连续的 '1' 经过右移后是会形成一对正三角和倒三角的,而矩形就在三角内,答案也在三角内。
也就是说我们可以先找到最长的连续的 '1' 的长度,根据这些长度我们可以做一个矩形面积的求解。可能的解为 1 * len、2 * (len-1)、3 * (len-2)......
一个 for 循环即可求解,同时也可以进行一个小优化,到了 len/2 的时候停止循环(因为左右相乘的结果对称相等)
但是,这样做完还是不对。因为没有考虑到特殊的情况,在求解 '1' 的最长长度时,可能会出现类似于 '101' 的组合出现(长度看似是 1)。在这样的排序下,当数组右移后,会出现 '110' 的结果,此时最大长度为 2.
对于这样的问题,我们将 s 成环 转化为 s+s 这样就解决了这个问题。
但也引入了新的问题,就是出现 '11' 全是 '1' 的这种情况的时候,成环反而出错,所以成环前特判一下。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
String s = sc.nextLine();
// 特判全是 '1' 的情况
if (s.indexOf('0') == -1) {
System.out.println((long) s.length() * s.length());
continue;
}
// 成环
s += s;
char[] chars = s.toCharArray();
// 找到最长的连续的 '1' 长度
long k = 0, max = 0;
for (char aChar : chars) {
if (aChar == '1')
max = Math.max(max, ++k);
else
k = 0;
}
// 暴力循环计算
for (int i = 1; i <= (max + 1) >> 1; i++) {
k = Math.max(i * (max - i + 1), k);
}
System.out.println(k);
}
}
}
E - Constructive Problem
题目大意:
题解:
由于MEX值只能增加 1 ,所以我们的操作需要:
- 删除数组中所有的 MEX+1,又因为我们仅仅能操作一次,让某个区间内的所有元素改为 x
- 添加 MEX,故我们可以 把 x = MEX
所以只要找到MEX+1在数组中的第一次出现和最后一次出现的下标设为 l,r ,则必须将 [l,r] (左闭右闭)范围内元素均改为 MEX。这是最小的范围,如果范围再增大,可能会覆盖掉一些别的元素,可能使得 MEX 值反而减小。因此从贪心角度,选取 [l,r] 改为 MEX 是最优的。
操作之后,计算操作后的 MEX 值,看是否与之前的值相比 增大了 1,输出结果
特别地,如果数组中没有出现MEX+1:
- 如果数组中每个元素都对MEX值有贡献(数组是 0,1,2,⋯,MEX-1 的一个排列),则答案是NO。
- 对于其他情况,选择一个冗余元素改为MEX即可。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int n = sc.nextInt();
// 输入
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(sc.nextInt());
}
// 将元素添加进 HashSet 集合
HashSet<Integer> all = new HashSet<>(list);
// 计算 MEX
int MEX1 = 0;
while (all.contains(MEX1))
MEX1++;
// 特殊情况 01234 等,修改不了
if (MEX1 == list.size()) {
System.out.println("No");
continue;
}
// 重新创建一个 HashSet,将 原来的元素去掉中间那一段的 MEX 到 MEX 的元素
all = new HashSet<>(n);
all.add(MEX1);
// b 就是用来剪枝的
boolean b = false;
for (Integer e : list) {
if (e == MEX1 + 1) {
b = true;
break;
} else {
all.add(e);
}
}
if (b) {
for (int i = n - 1; i >= 0; i--) {
Integer e = list.get(i);
if (e == MEX1 + 1) {
break;
} else {
all.add(e);
}
}
}
// 去掉中间那段元素后 重新求 MEX
int MEX2 = 0;
while (all.contains(MEX2))
MEX2++;
if (MEX1 + 1 == MEX2)
System.out.println("Yes");
else
System.out.println("No");
}
}
}
I - Lucky Numbers
题义:
奥林巴斯城最近推出了个人星舰的生产。现在火星上的每个人都可以买一个,然后以低廉的价格飞往其他星球。每艘星舰都有一个数字正整数z。让我们将数字z的幸运度定义为该数字的最大数字和最小数字之差。例如,142857的最大数字是8,最小数字是1,所以它的幸运度是8 - 1 = 7。数字111的所有数字都等于1,所以它的幸运度是0。Hateehc是一位著名的火星博主,他经常飞往太阳系的不同角落。为了更快地发布有趣的视频,他决定给自己买一艘星际飞船。当他来到商店时,他看到了数字从l到r的星际飞船。在商店里,海蒂想找一艘有最幸运数字的星际飞船。因为商店里有很多星际飞船,而Hateehc不会编程,所以你必须帮助博主编写一个程序来回答他的问题。
题解:
幸运数字一共有四种情况可以出现,分别是 以0、9结尾,还有输入的 a 或 b 本身。
对四种情况分别讨论。求得最大幸运(9)就剪枝。
关于为什么要 +- 10。因为 100 肯定是没有 90 幸运的。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int ans = 0;
int a = sc.nextInt();
int b = sc.nextInt();
int temp;
// 本题一共有四个分类,分别是以 0、9 结尾和输入的两个边界值
// 以 0 结尾的答案
temp = b / 10 * 10;
while (temp >= a) {
ans = fun(ans, temp);
temp -= 10;
if (fun(ans) == 9) {
break;
}
}
// 以 9 结尾的答案
temp = a / 10 * 10 + 9;
while (b >= temp) {
ans = fun(ans, temp);
temp += 10;
if (fun(ans) == 9) {
break;
}
}
// 边界答案
ans = fun(ans, a);
ans = fun(ans, b);
System.out.println(ans);
}
}
// 传两个值进去,返回 幸运值大的 元素
static int fun(int a, int b) {
return fun(a) > fun(b) ? a : b;
}
//求某个值的幸运值
static int fun(int x) {
return funMax(x) - funMin(x);
}
// 求位数上最大的值
static int funMax(int x) {
int t = x % 10;
while (x > 0) {
t = Math.max(t, x % 10);
x /= 10;
}
return t;
}
// 求位数上最小的值
static int funMin(int x) {
int t = x % 10;
while (x > 0) {
t = Math.min(t, x % 10);
x /= 10;
}
return t;
}
}
J - Playing in a Casino
题目大意:
给出一个 T 组样例,每组样例给出一个 n 和 m 表示给出n条数据,每条有 m 个数据
每次选两条,每个数据一一对应相减取绝对值,求绝对值的和是多少。
样例提示
3 5
1 4 2 8 5
7 9 2 1 4
3 8 5 3 1
|1−7|+|4−9|+|2−2|+|8−1|+|5−4|=19|1−3|+|4−8|+|2−5|+|8−3|+|5−1|=18
|7−3|+|9−8|+|2−5|+|1−3|+|4−1|=13|7−3|+|9−8|+|2−5|+|1−3|+|4−1|=13
19+18+13=5019+18+13=50
题解:
题义很明确,直接想到用暴力求解,把每列数据的每行两两组合相减 求绝对值相加的和就是结果。但是应该可以想到这个复杂度,肯定会炸的。
继续观察我们会发现每个数据对应的列的位置不变,改变数据所在的行数位置不会对答案造成影响
所以我们能通过排序将数据重新规划
还是拿样例来说,排序后就如下👇
1 4 2 1 1 3 8 2 3 4 7 9 5 8 5
所以我们可以对每一列进行排序,排序之后相减的结果都是正数,就可以不用取绝对值了。既然不用绝对值,我们就可以使用前缀和的思想,计算每列每个元素的贡献。贡献值为 后面所有的元素之和 减去 计算贡献的元素*后面元素的个数(因为要跟后面每个元素相减)。代码如下👇
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int n = sc.nextInt();
int m = sc.nextInt();
// 初始化集合
ArrayList<ArrayList<Long>> arrayLists = new ArrayList<>(m);
for (int i = 0; i < m; i++) {
arrayLists.add(new ArrayList<>());
}
// 按照一列一列的输入
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
arrayLists.get(j).add(sc.nextLong());
}
}
// 开 long 存储数据
long ans = 0;
// 计算每列的和
for (int i = 0; i < m; i++) {
ArrayList<Long> integers = arrayLists.get(i);
Collections.sort(integers);
// 使用Java 8的Stream API计算列表中元素之和
long sum = integers.stream().mapToLong(Long::longValue).sum();
// 计算一列中,每个元素的贡献,因为排过序了,所以不用取绝对值
// 故可以简化每次的加法,一次性将一个元素与其他所有元素相减
for (int j = 0; j < n - 1; j++) {
sum -= integers.get(j);
ans += sum - integers.get(j) * (n - j - 1);
}
}
System.out.println(ans);
}
}
}
K - Showstopper
题目大意:
给 a,b 两个数组,你可以进行无限次的操作,使得这两个数组最后一个元素最大。
- (1 <= i <= n)取一个 i,将数组中 a[i] 与 b[i] 调换
题解:
我们对每一列的元素进行排序,进行简单判断就好。水题
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int n = sc.nextInt();
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
arrayLists.add(new ArrayList<>(2));
}
for (int i = 0; i < n; i++) {
arrayLists.get(i).add(sc.nextInt());
}
for (int i = 0; i < n; i++) {
arrayLists.get(i).add(sc.nextInt());
}
ArrayList<Integer> r = arrayLists.get(n - 1);
Collections.sort(r);
boolean b = true;
for (int i = 0; i < n - 1; i++) {
ArrayList<Integer> integers = arrayLists.get(i);
Collections.sort(integers);
if (integers.get(0) > r.get(0) || integers.get(1) > r.get(1)) {
b = false;
break;
}
}
if (b)
System.out.println("YES");
else
System.out.println("NO");
}
}
}
L - Three Sevens
题目大意:
给定m天,每天n个人可以中奖,当前中奖的人不能参加后面的比赛,输出可能的方案
题目让我输出一个 没有冲突的方案,如果一个人在多天都能得奖,那么我们任选其中一天让他得奖,这种人可能存在多个,我们把同一天的这类人看成一组,这一天我们选择了这组中的其中一个人相当于选择了这个组,在之后就不能再选择属于这组中的人了
然后题目等价与在每天的那个组中选一个人,总的集合 向 当前天的选择的那个人(总的集合没有的人) 连边,然后这一天的这一组也属于 总的集合
组1 ---组2 -- 组3 ----...
== 组12 -- 组3 ---...
最后 每天 的 单独的一个集合 合并为一个大的集合 ,就说明有解,如果其中一个集合无法合并就说明无解
要保证前面选择的 不会和 后面的有冲突,我们从后面开始选,选完后把这组人全部标记
题解:
根据题义,只要在后面的日子里面参加了抽奖的 都不能作为前面中奖的,所以只要我们倒着将所有的天遍历,把一天里面所有的人全部放进一个集合里面,这个集合就是后面参与抽奖的人,只要在这个集合里面,就不能中奖了。只要一天之中有一个人没有在这个集合里面,就可以作为中奖的人。遍历每一天,每一天都可以有人中奖,则为YES,否则为NO。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int m = sc.nextInt();
// 初始化 输入
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>(m);
for (int i = 0; i < m; i++) {
int n = sc.nextInt();
ArrayList<Integer> integers = new ArrayList<>(n);
for (int j = 0; j < n; j++) {
integers.add(sc.nextInt());
}
arrayLists.add(integers);
}
// 使用 HashSet 去做全局的一个集合,在使用 contains 方法时可以快速的返回结果
// 如果使用 ArrayList、LinkedList 等数据结构,contains 方法需要遍历集合,耗时很长
HashSet<Integer> all = new HashSet<>();
ArrayList<Integer> ans = new ArrayList<>(arrayLists.size());
int k;
// 倒着遍历只要在后面出现过的人 都不能作为前面中奖的人
for (int i = arrayLists.size() - 1; i >= 0; i--) {
ArrayList<Integer> integers = arrayLists.get(i);
k = 0;
for (Integer integer : integers) {
// 只要有一个在后面没有出现过,就作为中奖的
if (!all.contains(integer)) {
k = integer;
break;
}
}
// 将后面出现过的人 全部添加到 HashSet 集合中
all.addAll(integers);
if (k == 0)
break;
else
ans.add(k);
}
// 输出结果
if (ans.size() == arrayLists.size()) {
for (int i = ans.size() - 1; i >=0; i--) {
System.out.print(ans.get(i) + " ");
}
System.out.println();
} else
System.out.println(-1);
}
}
}
N - Sum on Subarrays
题目大意:
构造题。
构造方案为,在前面全部填 2 后面填一个数,再后面填负无穷。
考虑在一串数后面加一个数的贡献,是所有后缀和(包括空后缀)加这个数大于 0 的个数。
每一个数字的贡献最多是这个数字的序号(因为这个数本身为正,然后分别和前面 n-1 的数的前缀和相加 都为正的话,最大贡献为 n)。
所以我们可以得到,一个 2 的贡献为 序号 i,那么所有的贡献值为 1,2,3,,,n。相加有公式 (1+n)*n/2。添加每一个 '2' 时,判断是否还需要 n 的贡献,如果不需要这么多,那么就根据还需要多少贡献去计算一个数字。如下,需要 1 的贡献,需要补充一个 -1 去和 第一个 2 相加,就可以补充一个贡献。
比如3 2 ——> 2 -1 -1000
由此可以推断,需要几个贡献就用👇
前缀和的取反 + 2*需要的贡献 + 1
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
sc.nextLine();
while (t-- != 0) {
int n = sc.nextInt();
int k = sc.nextInt();
for (int i = 1; i <= n; i++) {
if (k >= i * (i + 1) / 2)
System.out.print(2 + " ");
// 如果需要的贡献大于 0
else if (k - i * (i - 1) / 2 > 0)
System.out.print(-2 * i + (k - i * (i - 1) / 2) * 2 + 1 + " ");
else
System.out.print(-1000 + " ");
}
System.out.println();
}
}
}