- 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]);
else
set.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);
}
扩展:
如果原数组取值任意怎么办?