NOI Online 提高组第三题题解
T3.最小环
题面:
给定一个长度为 n 的正整数序列 ai,下标从 1 开始编号。我们将该序列视为一个首尾相邻的环,更具体地,对于下标为 i,j(i≤j) 的两个数 ai,aj,它们的距离为 min{j−i,i−j+n}。
现在再给定 m 个整数 k1,...,m,对每个 ki(i=1,2,...,m),你需要将上面的序列 ai 重新排列,使得环上任意两个距离为 ki 的数字的乘积之和最大。
n≤2×105。
举例/说明:
如果 n=6,并且 ai=i,k=1。则经过枚举可以得到一种最优方案是 {3,1,2,4,6,5},答案是 3×1+1×2+2×4+4×6+6×5+5×3=82。
如果 k=2,则可以得到一种最优方案是 {3,6,1,4,2,5},答案是 3×1+1×2+2×3+6×4+4×5+5×6=85。
如果 k=3,一种最优方案是 {1,5,3,2,6,4},这里注意答案是 88 而不是 44。
解答:
Solution 1
10 pts
一种很容易想到的做法,就是暴力枚举。先枚举一遍所有可能性,再根据每种可能性计算答案,再取一个最大值即可。没有任何思考技术含量。时间复杂度 O(n!),可以通过 n≤10 的测试点。
我觉得第二个测试点是 n≤18,可能也是给暴力分的,可以剪枝或者先推一推结论减小枚举范围,这样 20 分也就有了。
这样肯定是不行的。作为提高组的题目,肯定需要更进一步的思考。
Solution 2
30 pts
我们已经说过,优化时间的常见方法是转化为dp或者数学问题。
先来一个特殊情况:k=1。我们不妨把数据排一个序,然后一个一个插进序列中。假设插入的是 ai,ai 插入的位置与 ax,ay 相邻,则它对答案的贡献是 ai×(ax+ay)−ax×ay。可以这么理解:插入 ai 的时候,ai 与 ax,ay 都相邻了,答案增加 ai×(ax+ay);而 ax 与 ay 开始相邻,现在不相邻了,所以答案减去 ax×ay。
那么我们自然就会想到:ai×(ax+ay)−ax×ay 什么时候取最大值呢?考虑化简:−(ai−ax)(ai−ay)+ai2。由于已经排好序了,即对于任意的 i<j 均有 ai≤aj,则易得 ai−ax,ai−ay≥0。也就是说,ax,ay 越大,答案就越大。所以说最优取值就是 x=i−1,y=i−2。
综上考虑,再插入每一个数时,插进序列中最大的两个数之间。比如当 n=6 且 ai=i 时,插入的顺序是这样的:
{1}
{1,2}
{1,3,2}
{1,3,4,2}
{1,3,5,4,2}
{1,3,5,6,4,2}
可以保证每次序列中最大的两个数都是相邻的。所以说直接统计答案即可,没必要模拟插入的过程。时间复杂度 O(nlogn),瓶颈是排序的过程。
Solution 3
80 pts
好。既然我们已经处理了 k=1 的情况,我们就再看看 k>1 时如何做。就以 n=10 为例子吧,如果 k=2,4,则是这样的情况:
通过观察可以发现,对于这两种情况,可以将第 1,3,5,7,9 个元素为一组,2,4,6,8,10 为一组,这样的话对于每一组,就相当于是 k=1 的情况了。所以说,对于任意的 n,k,我们可以将其分为 gcd(n,k) 组,每组有 Q=gcd(n,k)n 个数,对于每一组处理 k=1 的情况即可。
但是我们又遇到一个问题:每一组的数取什么值好呢?
如果枚举每一组的数是多少,那么时间复杂度就会非常之大。所以我们还需要进行贪心。
考虑每一组的数经过排列后是 a1,a2,...,aQ,那么答案就是 a1×a2+...+aQ−1×aQ+aQ×a1。想必你们都听过小学奥数常说的一句话:积一定,差大和大。也就是说,对于一些数,它们的积是固定的,如果它们之间的差越大,也即数据越不平均,那么它们的和就越大。所以说我们自然想到了:设 n 个数从小到大排序是 a1,...,an。那么对于每一组的数,下标都是连续的。什么意思呢?就是将这些数分为 {a1,...,aQ},{aQ+1,...,a2Q},... 这些组,那么它们的答案之和就是最大的。
如果再回想一下的话,这种方法是有正确性的。考虑我们交换这些组中的两个数,可以证明答案减少了。
这样的复杂度是多少呢?对于每一组的处理是 O(Q),一共分成 gcd(n,k) 组,所以每次查询的时间复杂度是 O(n),总体时间复杂度是 O(n2)。仍然不可行。
Solution 4
100 pts
都 80 分了,离 AC 就不远了。
就看 n=10 的例子,如果我有两个询问,分别是 k=2,4,那么两个的答案就是一样的,因为 gcd(10,2)=gcd(10,4)=2。所以优化时间的方法就出来了:初始化。
我们枚举 i=1−2n,如果 gcd(n,i) 已经处理过了,那么就跳过;否则就处理一遍答案,并记录。这样对于每次查询,直接调用记录的结果就行,时间复杂度 O(1)。
那么初始化的时间复杂度是多少呢?就是 n 的因数个数再乘上 n,可以理解为 O(nn),足够通过这道题。网上有关于因数个数为 O(n) 的研究,有兴趣的同学可以去看一看。
总结一下,我们还是从暴力开始,这次我们走的是数学推导的道路,最终完全脱离了暴力的想法。之后想到优化就不难了,关键是数学推导的部分。
Stay hungry, stay foolish.