P6187 [NOI Online #1 提高組] 最小環
阿新 • • 發佈:2022-03-03
題目大意
給定一個長度為 \(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; }