Codeforces Round #636 (Div. 3) ——D. Constant Palindrome Sum 题解

题目链接:https://codeforces.com/contest/1343/problem/D

Codeforces Round #636 (Div. 3) ——D. Constant Palindrome Sum 题解_第1张图片Codeforces Round #636 (Div. 3) ——D. Constant Palindrome Sum 题解_第2张图片
题意是说改变给定数组中某些元素,使得a[i]与a[n - i - 1]的和为常数。

遍历每一对,假设这一轮读到的数是 x 和 y(x < y),那么改变其中一个,最大和最小的和是多少呢?

将较小的 x 变成 k,得到最大和 y + k
将较大的 y 变成 1,得到最小和 x + 1

所以对于某一对 (x, y),改变1个数情况下和的取值范围为(x + 1, y + k),改变2个数就能取到任意(2, 2k)中间的值。

但是遍历每一对的复杂度为 n / 2, 如果每次都记录整个区间的操作次数,时间复杂度要乘上 k, 就TLE了。所以今天学了新的差分数组,相当于前缀和的逆过程:只对某个区间的头和尾进行记录,最后整个区间计算前缀和即可。

例如,k = 3, 当前和为4,改变一个数的取值范围为(3, 5),那么根据前面的分析,如果最终选择的定值x在(3,5)区间内,只需要改变1次(或0次,对应4),而在区间外则需要改变2次。那么我们对差分数组做如下处理:
(1)diff[2] += 2, diff[2 * k] -= 2; 先初始化整个区间都需要2次
(2)diff[l] -= 1, diff[r + 1] += 1; 将(l, r)区间内的次数减1
(3)diff[sum] -= 1, diff[sum + 1] += 1;将sum对应的次数再减1变为0
(4)最后从头到尾,diff[i] += diff[i - 1],就将整个区间覆盖了(这里可以手写试一试)

代码如下:

#include 
using namespace std;
typedef long long ll;
int a[200010];
 
 
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n, k;
		scanf("%d %d", &n, &k);
		for(int i = 0; i < n; i++) {
			cin >> a[i];
		}
		int res[2 * k + 1] = {0};
		int sum;
		for(int i = 0, j = n - 1; i < j; i++, j--) {
			sum = a[i] + a[j];
			int l = sum - max(a[i], a[j]) + 1;
			int r = sum - min(a[i], a[j]) + k;
			res[2] += 2;
			res[l] -= 1;
			res[sum] -= 1;
			res[sum + 1] += 1;
			res[r + 1] += 1;
		}
		int ans = res[2];
		for(int i = 3; i <= 2 * k; i++) {
			res[i] += res[i - 1];
			ans = min(res[i], ans);
		}
		cout << ans << endl;
    }
    return 0;
}

你可能感兴趣的:(CF)