\(Solution\)

我们将序列分成几个互不干扰的环。(互不干扰就是一个环以 \(k\) 无论如何都到不了另一个环)

可以证明,我们会将其分成 \(gcd(n,k)\) 个环,每个环会有 \(\frac{n}{gcd(n,k)}\) 个数。(很多人的题解是这么写的,然而蒟蒻本蒻根本看不懂

我们接下来尝试证明一下:

首先,如果是一个环的话应该首尾相同。我们设每个环有 \(x\) 个数。那么就有:\(kx\equiv0\ (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\n", ans);
			continue;
		}
		int t = gcd(n, k), per = n / t;
		if(memo[per]) {printf("%lld\n", 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\n", ans);
		memo[per] = ans;
	}
	return 0;
} 
posted on 2020-03-10 16:01  Oxide  阅读(233)  评论(0编辑  收藏  举报