洛谷 P6187[2020 NOI Online 提高组] 最小环
题目描述:
思路:
首先,我们先随便选一组n和k,来画图理解一下本题的题意。
如图,我们以n = 10, k = 4的情况为例,先从1号点开始“跳”,每次向后跳4个数,这样就形成的图中红色五角星的路径。但是此时仍有一些数没有被走到过,因此我们又从2号点开始“跳”,跳完一个五角星后刚好把所有数都走到了一次。
根据上面的模拟,我们可以发现:将所有数围成一个环,那么我们只需要以若干个不同的数为起点,每次向后跳k个数地走,便可以走出若干个环,且这些环刚好可以把所有数走一边并不重复。
例如上图中的两个环为{1 -> 5 -> 9 -> 3 -> 7 -> 1}和{2 -> 6 -> 10 -> 4 -> 8 -> 2}。
而经过我们的观察与多次模拟后会发现,环的个数为gcd(n, k)。
也就是说,我们可以将所有数分成 gcdn, k) 个环,每个环中有n / gcd(n, k)个数。
那么现在,我们来看一下如何在这些环中求得最大值。
根据贪心的思想,在所有数已经确定的前提下,我们自然想要让最大的两个数乘在一起。而对于一个环中的任意一个数,它可以与它的前一项和后一项乘在一起。例如:
设一个环为{…-> a -> b -> c -> …},那么这三个数就可以贡献一共(a * b + b * c)点权值。
所以在所有数中最大的这种三元组为:
{max(2), max(1), max(3)},其中max(i)指的是所有数中第i大的数。根据这种思想,max(4)就应该放在max(2)左边,max(5)就应该放到max(3)右边… …直到整个环被装满。
也就是说,假设一共有loops个环,每个环中有nums个数,那么第一个环中存储的就是max(1)到max(nums),第二个环中存储的是max(nums + 1)到max(2 * nums + 1)… …第loops个环中存储的是max(loops * nums - nums - 1)到max(loops * nums)。这样我们就可以构造出一个最大方案了。
优化:
光是按照上面的思路来写,会超时。所以我们需要对搜索进行优化。
由于所有数字的大小和个数都已经固定了,所以如果有两个k使得两种情况下的总环数相等,那么它们的答案一定是相等的。例如:
n = 10
k1 = 4
k2 = 2
loops1 = gcd(n, k1) = 2
loops2 = gcd(n, k2) = 2 = loops1
则ans1 == ans2。
因此我们可以用记忆化搜索,用f[nums]存储当每个环中的数字个数为nums时的答案。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using</