P6187 [NOI Online #1 提高组] 最小环
题目大意
给定一个长度为 \(n\) 的正整数序列 \(a_i\),下标从 \(1\) 开始编号。我们将该序列视为一个首尾相邻的环,更具体地,对于下标为 \(i\), \((i \leqslant j)\) 的两个数 \(a_i\), \(a_j\),它们的距离为 \(\min(j-i, i+n-j)\)。
现在再给定 \(m\) 个整数 \(k_1\), \(k_2\),..., \(k_m\),对每个 \(k_i\)(\(i=1\), \(2\),..., \(m\)),你需要将上面的序列 \(a_i\) 重新排列,使得环上任意两个距离为 \(k_i\) 的数字的乘积之和最大。
思路
-
如果\(gcd(n,k_i)\ne 1\)则一定会形成多个部分
-
当\(k=1\)时,显然应该大的挨着大的,以最大的为中心,向两侧依次减少。
-
当\(k\ne 1\)且\(gcd(n,k)\ne 1\)时,我们考虑将\(k=1\)时的代价断开,每一段长\(\frac{n}{k}\),一共有\(k\)段。
例如: 6 5 4 3 2 1
在k=1时为:6×5 6×4 5×3 4×2 3×1 1×2
在k=2时为:6×5 6×4 5×4 3×2 2×1 3×1
不难发现,若在i和i+1处断开,则对答案的贡献为
-(a[i]*a[i+2]+a[i-1]*a[i+1])+(a[i]*a[i-1]+a[i+1]*a[i+2])
- 预处理出来,然后乱搞即可
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<iomanip>
#define LL long long
#define N 200010
using namespace std;
LL n,m,a[N],ans[N];
LL read(){
LL x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(LL)(ch-'0');ch=getchar();}
return x*h;
}
LL gcd(LL a,LL b){
return b==0 ? a:gcd(b,a%b);
}
void gans(LL k){
LL jp=n/k;
ans[k]=ans[1];
for(LL i=n-jp;i;i-=jp){
ans[k]-=(a[i+2]*a[i]+a[i-1]*a[i+1]);
ans[k]+=(a[i]*a[i-1]+a[i+1]*a[i+2]);
}
return ;
}
void solve(){
// solve q=0
for(LL i=1;i<=n;i++){
ans[0]+=(a[i]*a[i]);
}
// solve q=1
ans[1]+=a[n]*a[n-1];
for(LL i=n;i>2;i-=2){
ans[1]+=a[i]*a[i-2];
}
for(LL i=n-1;i>2;i-=2){
ans[1]+=a[i]*a[i-2];
}
ans[1]+=a[1]*a[2];
// solve other
for(LL i=2;i<=sqrt(n);i++){
if(n%i!=0)continue;
// solve i
gans(i);
// solve n/i
if(n/i<=n/2)gans(n/i);
}
}
int main(){
n=read(); m=read();
for(LL i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1);
solve();
for(LL i=1;i<=m;i++){
LL x=read();
//注意特判!!!
if(n==1){
printf("%lld\n",a[1]*a[1]);
continue;
}
if(x==0)printf("%lld\n",ans[0]);
else printf("%lld\n",ans[gcd(x,n)]);
}
return 0;
}

浙公网安备 33010602011771号