CF1343D Constant Palindrome Sum 题解
打比赛的时候这道题没想出来,等到想出来的时候比赛结束了…… q w q qwq qwq
下面进入正题:(前一部分我会复盘我的思路过程供大家参考,当然这是一个蒟蒻的思考过程, 大家完全可以跳过)
首先拿到题目,先想了下能否贪心,发现找不到贪心策略。毫无头绪,我开始尝试寻找突破口。这时我发现题目有一点比较奇怪:题目给了 k k k 的范围还保证 ∑ k ≤ 2 × 1 0 5 \sum k \leq 2 \times 10^5 ∑k≤2×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+an−i−1=x
中的
x
(
2
≤
x
≤
2
k
)
x(2 \le x \le 2k)
x(2≤x≤2k),算出每一对数需要替换多少个数,最后在所有的结果中取
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
2≤x≤k 时,除了
s
u
m
=
x
sum=x
sum=x 的一部分,剩下的两部分都要进行一次替换。还没开始想
k
<
x
≤
2
k
k < x \le 2k
k<x≤2k 的情况,我就发现我错了:
比方说给定的 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,an−i+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,an−i+1)+1,max(ai,an−i+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,an−i+1)+1,max(ai,an−i+1)+k] ( 1 ≤ i ≤ n 2 ) (1 \le i \le \dfrac{n}{2}) (1≤i≤2n) 覆盖了 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=2n−coveri
不要忘了我们的 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=coverx−equx+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;
}