CF1343D Constant Palindrome Sum 题解

也许更好的阅读体验

CF题目传送门

打比赛的时候这道题没想出来,等到想出来的时候比赛结束了…… q w q qwq qwq

下面进入正题:(前一部分我会复盘我的思路过程供大家参考,当然这是一个蒟蒻的思考过程, 大家完全可以跳过)


首先拿到题目,先想了下能否贪心,发现找不到贪心策略。毫无头绪,我开始尝试寻找突破口。这时我发现题目有一点比较奇怪:题目给了 k k k 的范围还保证 ∑ k ≤ 2 × 1 0 5 \sum k \leq 2 \times 10^5 k2×105。这就在暗示我们复杂度正常情况下应该含有 k k k(大部分题目还是没有废话的)。

接下来我观察到因为所有的数(初始的和替换的)都在 [ 1 , k ] [1,k] [1,k] 内,所以每一对数的和一定在 [ 2 , 2 k ] [2,2k] [2,2k] 内。

那么我们不妨考虑枚举题目中要求的等式
a i + a n − i − 1 = x a_i + a_{n-i-1} = x ai+ani1=x
中的 x ( 2 ≤ x ≤ 2 k ) x(2 \le x \le 2k) x(2x2k),算出每一对数需要替换多少个数,最后在所有的结果中取 min ⁡ \min min,时间复杂度为 O ( n k ) O(nk) O(nk)

但是这样会 TLE \texttt{TLE} TLE,为了不超时,基于枚举 x x x 的算法思想,时间复杂度要么 O ( k log ⁡ n ) O(k \log n) O(klogn) 要么 O ( k + n ) O(k+n) O(k+n)

首先我想出了一个解法,然而很遗憾是错的。这个想法是这样的(不想看的可以跳过):
把每一对数的总和进行排序,然后把所有的总和分成 s u m < x , s u m = x , s u m > x sum<x,sum=x,sum>x sum<x,sum=x,sum>x 三个部分。当 2 ≤ x ≤ k 2 \le x \le k 2xk 时,除了 s u m = x sum=x sum=x 的一部分,剩下的两部分都要进行一次替换。还没开始想 k < x ≤ 2 k k < x \le 2k k<x2k 的情况,我就发现我错了:

比方说给定的 a a a 1 , 2 , 2 , 3 1,2,2,3 1,2,2,3,求出总和排序后是 4 , 4 4,4 4,4。当 x = 2 x=2 x=2 时,我们会计算出替换次数为 2 2 2。可是 2 , 2 2,2 2,2 这一对数要想总和为 2 2 2 必须替换两次。所以替换次数应该为 3 3 3

好吧,只能换思路了。

接下来是正解:

我们先前的算法之所以是 O ( n k ) O(nk) O(nk),是因为我们在枚举 x x x 的过程中才计算每一对数需要替换多少个。为了优化,我们需要想办法进行预处理,算出对于每一个 x x x,有多少对数可以一个都不替换,有多少可以只替换其中一个,有多少必须两个都要替换。

不妨考虑各个击破:

一个都不替换

这个很简单,我们开一个数组 e q u equ equ,其中 e q u i equ_i equi 表示当 x = i x=i x=i 时有多少对数可以一个都不替换。 因为不用替换的条件就是一对数的和 s u m = x sum=x sum=x,那么我们只要算出每一对数的和 s u m sum sum,并且 equ[sum]++ 就可以了。

只需要替换其中一个

首先,对于一对数 a i , a n − i + 1 a_i,a_{n-i+1} ai,ani+1,在只替换其中一个数的情况下,它们的和只能在 [ min ⁡ ( a i , a n − i + 1 ) + 1 , max ⁡ ( a i , a n − i + 1 ) + k ] [\min(a_i,a_{n-i+1})+1, \max(a_i,a_{n-i+1})+k] [min(ai,ani+1)+1,max(ai,ani+1)+k] 中。我们稍微变通一下,求出有多少对数至多需要替换一个,那么需要替换一个的数目就很好求了,为至多需要替换一个的数目减去一个都不需要替换的数目。

那么如何求出有多少对数至多需要替换一个?这个问题就变成了:对于 x x x,求出有多少个区间 [ min ⁡ ( a i , a n − i + 1 ) + 1 , max ⁡ ( a i , a n − i + 1 ) + k ] [\min(a_i,a_{n-i+1})+1, \max(a_i,a_{n-i+1})+k] [min(ai,ani+1)+1,max(ai,ani+1)+k] ( 1 ≤ i ≤ n 2 ) (1 \le i \le \dfrac{n}{2}) (1i2n) 覆盖了 x x x

这一问题可以这样解决(而且对于类似的“求每一个数有多少个区间覆盖了此数”的问题都可以这样做):
开一个数组 c o v e r cover cover c o v e r i cover_i coveri 表示有多少个区间覆盖了数 i i i。首先枚举区间,设区间的左右端点分别为 l , r l,r l,r,然后我们进行操作 cover[l]++cover[r+1]--,枚举完后再用计算前缀和的方式进行如下操作: cover[i] += cover[i-1]。然后我们就求完了。

下面解释下算法的原理:其实我们 cover[l]++cover[i] += cover[i-1] 的两个操作等效于对于所有的 i ∈ [ l , r b o u n d ] i \in [l,rbound] i[l,rbound] 进行 cover[i]++。类似的,我们也进行了 cover[i]-- ( i ∈ [ r + 1 , r b o u n d ] ) (i \in [r+1, rbound]) (i[r+1,rbound])。前一个操作计算了有多少个区间的左端点小于等于 x x x,而后一个操作计算了有多少个区间的右端点小于 x x x。一加一减就是覆盖了 x x x 的区间数目。

如果还是难以理解,可以尝试自己举个栗子模拟一下,这里就不赘述了。

两个都需要替换

因为我们求出了 c o v e r cover cover,这一问题相当简单。一句话搞定:
n u m i = n 2 − c o v e r i num_i = \dfrac{n}{2} - cover_i numi=2ncoveri

不要忘了我们的 c o v e r i cover_i coveri 表示有多少对至多需要替换一个数。

所以我们对于每个 x x x,最少替换步数 a n s x ans_x ansx 就可以这样计算:
a n s x = c o v e r x − e q u x + n u m x × 2 ans_x = cover_x - equ_x + num_x \times 2 ansx=coverxequx+numx×2

最终时间复杂度 O ( k + n ) O(k+n) O(k+n)

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int equ[2 * maxn], cover[2 * maxn], a[maxn];

signed main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n, k;
		scanf("%d%d", &n, &k);
		for(int i = 1; i <= n; i++)
			scanf("%d", a + i);
      //注意值域最大为2k,初始化到2k+4比较保险。
		fill(equ, equ + 2 * k + 5, 0);
		fill(cover, cover + 2 * k + 5, 0);
		for(int i = 1; i <= n / 2; i++)
			equ[a[i] + a[n - i + 1]]++;
		for(int i = 1; i <= n / 2; i++)
		{
			cover[min(a[i], a[n - i + 1]) + 1]++;
			cover[max(a[i], a[n - i + 1]) + k + 1]--;//注意加上k之后还要加上1
		}
		for(int i = 3; i <= 2 * k; i++)//值域最小为2,所以从3开始做前缀和就可以了
			cover[i] += cover[i - 1];
		int ans = INT_MAX;
		for(int x = 2; x <= 2 * k; x++)
			ans = min(ans, cover[x] - equ[x] + (n / 2 - cover[x]) * 2);//这里就没有再开一个数组num了
		printf("%d\n", ans);
	}
	return 0;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值