//为啥题目名称叫最小环但题目要求最大环啊!!
作为一个在考场上差一点切掉这道题的蒟蒻,我还是来发个题解说一下
这道题考的东西还是蛮多的,有思维,前缀和,数论,找规律
暴力很好打,有 20 20 20分,爆搜即可
然后思考正解,对于一个 k k k,我们讲每个 a i a_{i} ai与 a ( i − k + n ) % n a_{(i-k+n)\%n} a(i−k+n)%n建边,那么肯定会得到一些环,我们以k为距离来跳跃边,则当跳跃 s s s次且 k s ks ks被 n n n整除时就形成了一个环。那么我们就要求 s s s
因为 k s ks ks一定是 n n n的倍数且 s s s最小,那么 k s ks ks就是 lcm ( k , n ) = n k gcd ( n , k ) \operatorname{lcm}(k,n)=\frac{nk}{\gcd(n,k)} lcm(k,n)=gcd(n,k)nk,那么 s = n gcd ( n , k ) s=\frac{n}{\gcd(n,k)} s=gcd(n,k)n
那么环上点的个数确定了,接下来要决定使环上点权值最大的方案了。
定理1:当 a + b = 2 n a+b=2n a+b=2n时, a b ab ab的最大值为 n 2 n^2 n2,且此时 a = b = n a=b=n a=b=n。
证明:若 a a a不为 n n n,设 a = n − x , b = n + x a=n-x,b=n+x a=n−x,b=n+x,那么ab= ( n − x ) ( n + x ) = n 2 − x 2 (n-x)(n+x)=n^2-x^2 (n−x)(n+x)=n2−x2,又因为 n 2 n^2 n2与 x 2 x^2 x2均非负,且 x ≠ n x\neq n x=n,小于原式。证毕。
有了这个定理,那么每个数肯定要和最近的数去配对,那么应该是每一段中的数都是在排好序 a a a的中是连续的。设一个降序的排好序的环上的权值数列为 f f f, f n f_n fn肯定要和 f n − 1 f_{n-1} fn−1与 f n − 2 f_{n-2} fn−2配对,以此类推,那么问题又来了, f x , f x + 1 f_x,f_{x+1} fx,fx+1要怎么和 f x + 2 , f x + 3 f_{x+2},f_{x+3} fx+2,fx+3配对呢?
无非两种配法,最后证明出来是 f x , f x + 2 f_x,f_{x+2} fx,fx+2为一对,其余为一对,具体证明过程留给读者自行思考
那么这道题不就好做了吗? O ( n ) O(n) O(n)枚举 k k k, O ( n ) O(n) O(n)枚举统计答案,询问时直接输出答案。
那么恭喜你获得了 80 80 80分的好成绩,我考试时也是栽在这里。看来我对前缀和还不太熟。
代码实现:
#include<cstdio>
#include<algorithm>
using namespace std;
long long n,m,a[200039],f[200039],s[200039],fs[200039],k,x,y,tot,pus,ans;
int main() {
// freopen("ring.in","r",stdin);
//freopen("ring.out","w",stdout);
register int i,j,h;
scanf("%lld%lld",&n,&m);
for(i=1; i<=n; i++)scanf("%lld",&a[i]),ans+=a[i]*a[i];
sort(a+1,a+n+1);
f[0]=ans;
for(i=1; i<=m; i++) {
ans=0;
scanf("%lld",&k);
if(f[k]){printf("%lld\n",f[k]);continue;}
tot=1;
pus=1;
do {
pus=(pus+k-1)%n+1;
tot++;
} while(pus!=1);
tot--;
//printf("%lld\n",tot);
for(j=1; j<=n/tot; j++) {
ans+=a[(j-1)*tot+1]*a[(j-1)*tot+2]+a[j*tot]*a[j*tot-1];
for(h=(j-1)*tot+3; h<=j*tot; h++)ans+=a[h]*a[h-2];
}
f[k]=ans;
printf("%lld\n",ans);
}
}
我们注意到,循环中有这样一行代码:
for(h=(j-1)*tot+3; h<=j*tot; h++)ans+=a[h]*a[h-2];
这明显是可以前缀和优化的,可以优化成 O ( n g ( n ) ) O(ng(n)) O(ng(n)), g g g为欧拉函数, g ( x ) g(x) g(x)表示 x x x的约数个数。那么我们得到代码:
#include<cstdio>
#include<algorithm>
using namespace std;
long long n,m,a[200039],f[200039],s[200039],fs[200039],k,x,y,tot,pus,ans,q[200039];
int main() {
//freopen("ring.in","r",stdin);
//freopen("ring.out","w",stdout);
register int i,j,h;
scanf("%lld%lld",&n,&m);
for(i=1; i<=n; i++)scanf("%lld",&a[i]),ans+=a[i]*a[i];
sort(a+1,a+n+1);
f[0]=ans;
for(i=3;i<=n;i++) q[i]=q[i-1]+a[i]*a[i-2];
for(i=1; i<=m; i++) {
ans=0;
scanf("%lld",&k);
if(f[k]){printf("%lld\n",f[k]);continue;}
tot=__gcd(n,k);
pus=n/tot;
//printf("%lld\n",tot);
for(j=1; j<=tot; j++) {
ans+=a[(j-1)*pus+1]*a[(j-1)*pus+2]+a[j*pus]*a[j*pus-1]+q[j*pus]-q[(j-1)*pus+2];
}
f[k]=ans;
printf("%lld\n",ans);
}
}
这道 T G TG TG的 T 3 T3 T3就被我们很容易地切掉了