CF-Round#636-div3-D题
D. Constant Palindrome Sum
这道题是贪心 + 差分数组~
题目大意:给定长度为n的数组和一个k,数组中的每个元素数据范围都在[1, k]之间。要求最少次数的替换其中的某些元素
使得:
1.数组中的每个元素的范围都在[1, k]之间
2.把数组分为n / 2个对,其中a[i] 与 a[n - i + 1]组成一对,所有对之和均等于x。
然后输出最少替换次数
本题思路:
这道题我们思考的方向就是找到最优解x,使得替换的元素最少,因为我们已经把这些元素分成一对一对了,那么对于一对一对,我们只有下面三种操作:
1.一对中两个元素均不需要被改变(这当然是我们最希望的)
2.一对中一个元素需要被改变
3.一对中两个元素都需要被改变(这当然是我们最不希望的)
所以这三种操作就分别对应了三种情况(归根结底我们就是需要找到最优解x,这个x应该设置为多少才可以最少改变元素):
-
对于第一种情况:
我们只需要统计一下每对的和是多少,计数即可,和作为下标。保存在cnt[]中。
cnt[i]代表和为i的对数有多少 -
对于第二种情况:
需要改变一个元素的情况,我们不妨转化成为至多改变一个元素,我们我们就需要统计一下这些和覆盖了哪些元素,我们应该寻找怎样合适的x使得改变次数最少。
那么我们先来看看一对元素和的范围是多少。
范围在[min(a[i], a[n - i + 1]) + 1, max(a[i], a[n - i + 1]) + k)];
因为a[i]的范围是[1, k]嘛,我们取一对中的最小然后+1,这就对应着我们的左边界;取一对中的最大然后+k,这就对应着我们的右边界。
如果我们所设置的x在上面这个区间范围内,我们就只需要至多改变一个元素使得和为x。反之,如果所设定的x在这个区间范围外,我们就必须改变两个元素了,这个情况后面再说。
我们用pre[]数组标记覆盖当前数的区间有多少
pre[i]表示覆盖i这个点的区间数有多少
作如下操作:
pre[min(a[i], a[n - i + 1]) + 1]++;
pre[max(a[i], a[n - i + 1]) + k) + 1]–;
我们遍历完n / 2个对之后,求前缀和就可以得到覆盖下标这个点的区间数有多少了。
求前缀和:pre[x] += pre[x - 1];
说一下为什么要做 pre[max(a[i], a[n - i + 1]) + k) + 1]–;这个–操作
所以我们求得pre[]之后,pre[i]代表覆盖i得区间有多少,至多改变一个元素得区间有多少,所以我们求改变一个元素得区间就直接pre[i] - cnt[i]即可(cnt[i]表示原数组中某些对的和已经是i的区间数有多少) -
对于第三种情况:
我们求得了上面得pre[]数组后,这种情况就好办了,除pre[i]得数目区间之外得其他区间都是需要改变两个元素得,所以式子是n / 2 - pre[i];
(再次强调pre[i]代表覆盖i的区间有多少个)
好啦~我说完啦
代码部分:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, k;
int main()
{
int t;
cin >> t;
while (t--)
{
scanf ("%d%d", &n, &k);
vector<int> a(n + 5);
vector<int> cnt(k * 2 + 5);
for (int i = 1; i <= n; i++)
{
scanf ("%d", &a[i]);
}
int t = n / 2;
for (int i = 1; i <= t; i++)
{
++cnt[a[i] + a[n - i + 1]];
}
vector<int> pre(2 * k + 5);
for (int i = 1; i <= t; i++)
{
int l1 = 1 + a[i];
int r1 = k + a[i];
int l2 = 1 + a[n - i + 1];
int r2 = k + a[n - i + 1];
++pre[min(l1, l2)];
--pre[max(r1, r2) + 1];
}
for (int i = 1; i <= 2 * k + 1; i++)
{
pre[i] += pre[i - 1];
}
int ans = 1e9;
k *= 2;
for (int i = 2; i <= k; i++)
{
ans = min(ans, (pre[i] - cnt[i]) + (t - pre[i]) * 2);
}
cout << ans << endl;
}
return 0;
}