【NOI Online 提高组】最小环

(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;
} 
原文地址:https://www.cnblogs.com/AWhiteWall/p/12456259.html