- New Year Parties">CF 611 E. New Year Parties
- F. Puzzle">CF 802 F. Puzzle
- A. Sorting Railway Cars">CF 605A. Sorting Railway Cars
CF 611 E. New Year Parties
输入 n(<=2e5) 和长为 n 的数组 a(1<=a[i]<=n)。
对于每个 a[i],你可以将其 +1 或 -1,每个 a[i]至多修改一次。
输出修改后的 a 的不同元素个数的最小值和最大值。
不同元素个数即 len(set(a))。
思路:
最大值 : 排序 + 贪心
能减就减,不能减的话,能保持尽量保持,不能保持,就加一
最小值:计数 + 贪心
考虑上一个使用的位置,能和就和,不能和就加一(向上靠拢)
static final int N = 200010;static int[] a = new int[N], b = new int[N];static int n;static void solve() {n = ni();for (int i = 0; i < n; i++) {a[i] = ni();b[a[i]]++;}Arrays.sort(a, 0, n);int max = 0;Set<Integer> set = new HashSet<>();for (int i = 0; i < n; i++) {if (!set.contains(a[i] - 1))set.add(a[i] - 1);else if (!set.contains(a[i]))set.add(a[i]);elseset.add(a[i] + 1);}max = set.size();int min = 0, pre = -1;for (int i = 1; i <= n; i++) {if (b[i] == 0) continue;if (pre == i - 1 || pre == i) {continue;} else {min++;pre = i + 1;}}out.println(min + " " + max);}
CF 802 F. Puzzle
给定2行n列的01原数组,以及2行n列的目标数组,问将原数组通过交换相邻位置元素,最少需要多少步能变至目标数组,若无合法方案,输出-1
输入:
第一行表示n
第2,3行为各有n 个用空格分隔的数字,表示原数组
第4,5行为各有n 个用空格分隔的数字,表示目标数组
输出
最小步数,若没有,输出-1
思路:
将原数组中的1按是否在目标集合中分为两类,第一种是变换前已经在目标位置中,第二种是在变换前不在目标位置中。
先考虑1维的情况
如果某个1已经在目标集和中,说明不用对其进行操作,代价为0
如果某个1不在目标集和中,说明需要将其移动至目标集和中,代价移动步数
具体做法:将在集和中的已经有数字的位置记为0,没有数字的记为-1,将在集和外的数字对应位置记为1
从前往后,计算前缀和,若当前前缀和x大于0,说明有x个数还未移动至目标集中,每个都还需1步,总代价res += x
若当前前缀和x小于0,说明有x个目标集中的位置缺数,总代价为res += -x
考虑二维情况
如果某一位置,两维前缀和异号,说明可将上面一个数字移动至下边的目标集,或者将下面的一个数字移动至上面的目标集中,总代价 +1.
static final int N = 200010;static int[][] a = new int[2][N];static int[][] b = new int[2][N];static int n;static void solve() {n = ni();int s1 = 0, s2 = 0;for (int i = 0; i < 2; i++) {for (int j = 1; j <= n; j++) {a[i][j] = ni();s1 += a[i][j];}}for (int i = 0; i < 2; i++) {for (int j = 1; j <= n; j++) {b[i][j] = ni();s2 += b[i][j];}}if (s1 != s2) {out.println(-1);return;}for (int i = 1; i <= n; i++) {if (b[0][i] == 1) {if (a[0][i] == 1)a[0][i] = 0;else a[0][i] = -1;}if (b[1][i] == 1) {if (a[1][i] == 1)a[1][i] = 0;else a[1][i] = -1;}}long p1 = 0, p2 = 0;long res = 0;for (int i = 1; i <= n; i++) {p1 += a[0][i];p2 += a[1][i];if (p1 * p2 >= 0)res += Math.abs(p1) + Math.abs(p2);else {long x = Math.min(Math.abs(p1), Math.abs(p2));if (p1 < 0) p1 += x;else p1 -= x;if (p2 < 0) p2 += x;else p2 -= x;res++;res += Math.abs(p1) + Math.abs(p2);}}out.println(res);}
CF 605A. Sorting Railway Cars
题意描述:
给定一个长度为n的数组,不超过100000。
数组中的所有数正好是1-n的某个排列。
每次操作如下:任选一个数放在队头或队尾
问最少几次操作使数组按递增顺序排列?
输入:
第一行,一个整数n表示数组长度
第二行,空格隔开的n个整数(ai != aj && 1 <= ai,aj <= n)
思路:
一次移动一定能将一个数字放到其应在的位置
排好序后的序列和原序列相比,考虑有哪些数没有移动过!
没有动过的数中间一定不会再有别的数字
即找数值连续的最长上升子序列或排序后的下标最长递增子数组(连续)
// 数值连续的最长上升子序列static int N = 100010;static int[] a = new int[N];static int[] f = new int[N];static int n;static void solve() {n = ni();for (int i = 1; i <= n; i++) a[i] = ni();int max = 0;for (int i = 1; i <= n; i++) {f[a[i]] = f[a[i] - 1] + 1;max = Math.max(max, f[a[i]]);}out.println(n - max);}
// 下标最长递增子数组static int N = 100010;static int[][] a = new int[N][2];static int[] b = new int[N];static int[] f = new int[N];static int n;static void solve() {n = ni();for (int i = 1; i <= n; i++) {a[i][0] = ni();a[i][1] = i;}Arrays.sort(a, 1, n + 1, (o1, o2) -> (o1[0] == o2[0] ? o1[1] - o2[1] : o1[0] - o2[0]));int max = 0;int c = 0;for (int i = 1; i <= n; i++) {if (a[i][1] > a[i - 1][1]) {c++;} else {c = 1;}max = Math.max(max, c);}out.println(n - max);}
扩展:
如果原数组取值任意怎么办?
