原题链接
简洁题意:
一个环上有数字,给定一个距离 k k k,构造一个序列使 ∑ i = 1 n ( a i × a i + k ) \sum^{n}_{i=1}(a_i\times a_{i+k}) ∑i=1n(ai×ai+k)最大。这里指的是环上加减。
分析:
这个题很容易想到一个策略,把两个大的相乘很明显更划算。于是能想到由
1
1
1开始
b
f
s
bfs
bfs,扩展出来左右两边的数,从大到小放数字。但这样很容易出现一个问题:有些永远不可能搜到!比如题目中的样例,
k
=
3
k=3
k=3的情况就有一半会搜不到。有一种解决方法:标记每一位,像
t
a
r
j
a
n
tarjan
tarjan一样,每一个点看看有没有标记,没有标记就搜。这种方法时间复杂度是
Θ
(
n
)
\Theta(n)
Θ(n)的,可行,但是我没有试过。这是考试的时候想的,但是时间不够,就没写了。下面说一个代码简洁的方法:
针对上面的问题不难发现,这样分出来每一次搜索可以划分为一组。那么一组有多少个呢?就是
n
÷
g
c
d
(
n
,
k
)
n\div gcd(n,k)
n÷gcd(n,k)个。这个我不会证明,纯粹是一个一个试出来规律的。要是有巨佬会证明的在评论区说一下,我会加上并特别鸣谢的。于是,分成了很多组就好办了。每一组互不干扰,再根据前面
b
f
s
bfs
bfs的方法,发现一组中分到的数字是连续的。不难发现,在一组中除了最大和次大、最小和次小相乘,这一组里面都是
a
i
+
2
×
a
i
a_{i+2}\times a_{i}
ai+2×ai的。注意,为了方便放大的数,这里的
a
a
a是已经从大到小排序的。于是,我们也可以每次
i
i
i加上一组的个数,这样就相当于把
a
a
a的一部分分给前一组了。时间复杂度也是
Θ
(
n
)
\Theta(n)
Θ(n)的。
针对上面两种方法,时间复杂度仍然不够用,因为会有很多个
k
k
k要求。但是发现,
k
k
k最大小于
n
÷
2
n\div2
n÷2,也就是
1
0
5
10^5
105种
k
k
k要求,但是却会有
1
0
5
×
2
10^5\times2
105×2个
k
k
k。如果每个分别求,会
T
L
E
TLE
TLE几个点。但是,求的个数比种数多很多,肯定有很多重复。所以可以用一个
f
f
f数组存每个
k
k
k的答案,遇到重复的直接输出。但是这样时间仍然很紧,不难发现只要分的组是一样的答案就是一样的。我们又能少输出很多个
k
k
k了,注意这样优化第一个方法就也要求一个一组的个数。
总时间复杂度
Θ
(
k
n
)
\Theta(kn)
Θ(kn)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int NN=2e5+4;
long long a[NN],f[NN];
int gcd(int a,int b)
{
return !b?a:gcd(b,a%b);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
sort(a+1,a+1+n);
while(m--)
{
int k;
scanf("%d",&k);
long long ans=0;
if(k==0||n==1)
{
if(f[0])
{
printf("%lld\n",f[0]);
continue;
}
for(int i=1;i<=n;i++)
ans+=a[i]*a[i];
f[0]=ans;
printf("%lld\n",ans);
continue;
}
int team=n/gcd(n,k);
if(f[team])
{
printf("%lld\n",f[team]);
continue;
}
for(int i=1;i<=n;i+=team)
{
for(int j=0;j<team-2;j++)
ans+=a[i+j]*a[i+j+2];
ans+=(a[i]*a[i+1]+a[i+team-1]*a[i+team-2]);
}
f[team]=ans;
printf("%lld\n",ans);
}
return 0;
}