(Solution)
我们将序列分成几个互不干扰的环。(互不干扰就是一个环以 (k) 无论如何都到不了另一个环)
可以证明,我们会将其分成 (gcd(n,k)) 个环,每个环会有 (frac{n}{gcd(n,k)}) 个数。(很多人的题解是这么写的,然而蒟蒻本蒻根本看不懂)
我们接下来尝试证明一下:
首先,如果是一个环的话应该首尾相同。我们设每个环有 (x) 个数。那么就有:(kxequiv0 (mod n))。
我们用 (gcd(n,k)) 把 (n) 与 (k) 划分成几个小格子,那么 (frac{n}{gcd(n,k)}) 就是 (n) 有几个这样的小格子。我们将其乘以 (k),肯定是满足 (k) 走几步是能达到的,那么对于 (n),相当是将 (frac{n}{gcd(n,k)}) 个小格子的容量由 (gcd(n,k)) 扩展到了 (k) 个,也是满足前面同余的需求的。(注意必须使之前小格子的容量为 (k) 的因数,这样才能保证倍增)
接下来我们要保证的是每个环有 (frac{n}{gcd(n,k)}) 个数的时候那个环的每个数只能走一次。(其实这里描述并不准确,因为个数本身就是只出现一次的)
这个其实很好证,因为 (gcd) 就是最大的公共因数,所以 (frac{n}{gcd(n,k)}) 就是最小的,即 (x)。
然后关于为什么是按顺序放的我就在这里放一个大佬的证明:点此看神仙
个数固定的一定答案是一样的,我们搞一个记忆化就可以了。(毕竟一个数的因子个数并不多)
详见代码。
(Code)
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
ll a[N], memo[N], ans;
int n, m, k;
int read() {
int x = 0, f = 1; char s;
while((s = getchar()) > '9' || s < '0') if(s == '-') f = -1;
while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
return x * f;
}
int gcd(const int a, const int b) {
if(! b) return a;
return gcd(b, a % b);
}
int main() {
n = read(), m = read();
for(int i = 1; i <= n; ++ i) a[i] = read();
sort(a + 1, a + n + 1);
while(m --) {
k = read();
ans = 0;
if(k == 0 || n == 1) {
for(int i = 1; i <= n; ++ i) ans += a[i] * a[i];
printf("%lld
", ans);
continue;
}
int t = gcd(n, k), per = n / t;
if(memo[per]) {printf("%lld
", memo[per]); continue;}
for(int i = 1; i <= n; i += per) {
for(int j = 0; j < per - 2; ++ j) ans += a[i + j] * a[i + j + 2];//按顺序取,相当是 min,min+2...min+3,min+1 (这是个环)
ans += a[i] * a[i + 1] + a[i + per - 1] * a[i + per - 2];//取min与min+1,max与max-1
}
printf("%lld
", ans);
memo[per] = ans;
}
return 0;
}