题目大意
威廉姆有
n
n
n个整数的数列
a
1
,
a
2
,
…
a
n
a_1,a_2,…a_n
a1?,a2?,…an?,他每次可以交换相邻的两个数。威廉姆想请你计算出他交换的最小次数,使得数列中相邻的元素奇偶性不同。
输入描述
第一行包含一个整数
t
(
1
≤
t
≤
1
0
4
)
t(1≤t≤10^4)
t(1≤t≤104),表示测试用例的组数。每组测试用例的第一行包含一个
n
(
1
≤
n
≤
1
0
5
)
n(1≤n≤10^5)
n(1≤n≤105),表示数列中元素的个数。第二行包含
n
n
n个整数
a
1
,
a
2
,
…
,
a
n
(
1
≤
a
i
≤
1
0
9
)
a_1,a_2,…,a_n(1≤a_i≤10^9)
a1?,a2?,…,an?(1≤ai?≤109)表示威廉姆的数列。保证所有测试用例中
n
n
n的和不超过
1
0
5
10^5
105。
输出描述
对于每组测试用例输出最小的操作次数。如果无论如何都不能达成数列中相邻的元素奇偶性不同,输出
?
1
-1
?1。
样例输入
5
3
6 6 1
1
9
6
1 1 1 2 2 2
2
8 6
6
6 2 3 4 5 1
样例输出
1
0
3
-1
2
思路
这个题非常不容易想到的地方就是可以直接判断每个位置是奇数还是偶数。 首先,统计奇数的数量和偶数的数量,如果二者之差的绝对值大于1,是一定不可能得到想要的结果的。然后,我们有三种情况,分别是:
(
i
)
(i)
(i)奇数数量比偶数数量多1:
a
1
,
a
3
,
…
,
a
n
a_1,a_3,…,a_n
a1?,a3?,…,an?是奇数,
a
2
,
a
4
,
…
,
a
n
?
1
a_2,a_4,…,a_{n-1}
a2?,a4?,…,an?1?是偶数。
(
i
i
)
(ii)
(ii)偶数数量比奇数数量多1:
a
1
,
a
3
,
…
,
a
n
a_1,a_3,…,a_n
a1?,a3?,…,an?是偶数,
a
2
,
a
4
,
…
,
a
n
?
1
a_2,a_4,…,a_{n-1}
a2?,a4?,…,an?1?是奇数。
(
i
i
i
)
(iii)
(iii)奇数数量等于偶数数量,此时又分为两种情况:
①
①
①
a
1
,
a
3
,
…
,
a
n
?
1
a_1,a_3,…,a_{n-1}
a1?,a3?,…,an?1?是偶数,
a
2
,
a
4
,
…
,
a
n
a_2,a_4,…,a_n
a2?,a4?,…,an?是奇数。
②
②
②
a
1
,
a
3
,
…
,
a
n
?
1
a_1,a_3,…,a_{n-1}
a1?,a3?,…,an?1?是奇数,
a
2
,
a
4
,
…
,
a
n
a_2,a_4,…,a_n
a2?,a4?,…,an?是偶数。 我们无法证明出这两种情况中的哪种是最优情况,因此不能只对其中一种情况求解。事实上,通过枚举一些可能的样例,得到这两种情况之间不存在必定最优情况,需要我们都去判断一遍,求两种情况中步数最小的情况。
我们在每种情况中,逐一判断每一位,如果该位的奇偶性与情况不同,则从该位开始向后寻找我们想要的那个奇数或偶数。整个过程依靠尺取法(双指针)完成。已经交换完成的位置就不用去管了,继续向后判断,整个数组只需要扫描一遍,不会超时。每次交换的时间复杂度为
O
(
1
)
O(1)
O(1),给出如下证明:
已经事先将数列变更为只表示奇偶性的
01
01
01数列,设第
i
i
i位为奇数,但是我们想要这一位是偶数。由于我们向右选取最靠左的偶数,位置为
j
j
j,因此这个偶数向左直到这个奇数的所有数全部为奇数。由传递性知,交换这两个数只需要
j
?
i
j-i
j?i步,利用
s
w
a
p
swap
swap函数即可,无需模拟整个交换过程。
考虑贪心的思想。若选第二靠左的偶数,则不能保证传递性。交换后,原位置
(
i
+
1
)
(i+1)
(i+1) ~
(
j
?
1
)
(j-1)
(j?1)之间的数整体右移,下一次再选原先最靠左的偶数,所需步数相比原来一定不是最小步数,故贪心地选取是最优策略。
考点
尺取法 贪心
AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int t, n, ans_f, ans_s, ans, a[maxn], b[maxn];
void init() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> t; }
int main()
{
init();
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i], b[i] = a[i];
int odd = 0, eve = 0, ans_f = 0, ans_s = 0, ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] % 2 == 1)odd++, a[i] = 1, b[i] = 1;
else eve++, a[i] = 0, b[i] = 0;
}
if (abs(odd - eve) > 1) {
cout << -1 << endl;
continue;
}
if (eve != odd) {
for (int cur, k = 1, i = 1, j = 1; k < n; k++) {
if (eve > odd)cur = (k + 1) % 2;
else if (eve < odd)cur = k % 2;
if (i <= k) {
i = k + 1;
while (a[i] == 0 && i < n)i++;
}
if (j <= k) {
j = k + 1;
while (a[j] == 1 && j < n)j++;
}
if (a[k] != cur) {
if (cur == 1) {
swap(a[k], a[i]);
ans += (i - k);
while (a[i] == 0 && i < n)i++;
}
else if (cur == 0) {
swap(a[k], a[j]);
ans += (j - k);
while (a[j] == 1 && j < n)j++;
}
}
}
cout << ans << endl;
}
else {
for (int cur, k = 1, i = 1, j = 1; k < n; k++) {
cur = (k + 1) % 2;
if (i <= k) {
i = k + 1;
while (a[i] == 0 && i < n)i++;
}
if (j <= k) {
j = k + 1;
while (a[j] == 1 && j < n)j++;
}
if (a[k] != cur) {
if (cur == 1) {
swap(a[k], a[i]);
ans_f += (i - k);
while (a[i] == 0 && i < n)i++;
}
else if (cur == 0) {
swap(a[k], a[j]);
ans_f += (j - k);
while (a[j] == 1 && j < n)j++;
}
}
}
for (int i = 1; i <= n; i++)a[i] = b[i];
for (int cur, k = 1, i = 1, j = 1; k < n; k++) {
cur = k % 2;
if (i <= k) {
i = k + 1;
while (a[i] == 0 && i < n)i++;
}
if (j <= k) {
j = k + 1;
while (a[j] == 1 && j < n)j++;
}
if (a[k] != cur) {
if (cur == 1) {
swap(a[k], a[i]);
ans_s += (i - k);
while (a[i] == 0 && i < n)i++;
}
else if (cur == 0) {
swap(a[k], a[j]);
ans_s += (j - k);
while (a[j] == 1 && j < n)j++;
}
}
}
cout << min(ans_f, ans_s) << endl;
}
}
return 0;
}
心得
这个题考试的时候把我卡了两个多小时,我完全没想到最近B题都做不出了,好歹原来AB题都是稳定出。主要是我看到这个数据范围没有直接去思考如何直接扫描一遍数组解决问题,因此没有想到是用尺取法。考试的时候纯粹是贪心过样例,然后一直WA,比赛后看了题解,由于看得不是很细致,导致前后一共WA了10发才AC。今天调这个题,从上午10点调到中午1点才调对。
以后遇到这类问题,首先看数据范围,考虑是否需要优雅的暴力。其次,不要盲目地猜结论,如果没有办法去证明结论,一定不要对着结论死磕。就比如对于上述第三种情况就需要再分成两种情况比较,我当时就盲目地猜测第一个数是什么奇偶性,那么这个数列就只能按照第一个数是这种奇偶性的情况去排。事实上第一个数的奇偶性不能影响全局,如此多的数,当然不可能全部由第一个数去决定后面的奇偶性,后面情况复杂了,会有多种变化。
总之,这次比赛体验很糟糕,下次比赛再努力吧,希望不会犯同样的错误,能够对结论的推导与选择收放自如。
|