CF-Round#636-div3-D题

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]–;这个–操作
    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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娃娃酱斯密酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值