J - 乌龟与最大公约数
题解:最大化分割子集和的 GCD
题目解析
给定连续整数集合 \(\{a,\ a+1,\ a+2,\ \dots,\ a+b-1\}\),将其分成两个非空子集,求两个子集和的最大公约数的最大可能值。
枚举
首先我们考虑暴力,枚举子集的复杂度为\(2^n\),故不行,所以我们考虑优化。
核心数学推导
- 计算全集总和
连续 \(b\) 个数的和:
\[S = a + (a+1) + \dots + (a+b-1) = \frac{b \cdot (2a + b - 1)}{2}
\]
- 关键性质1
设两个子集和为 \(S_1, S_2\),则 \(S_1 + S_2 = S\)。
对任意公约数 \(d\),满足 \(d \mid S_1\) 且 \(d \mid S_2\),则必然有 \(d \mid S\)。
因此:答案一定是总和 \(S\) 的约数,我们只需要找 \(S\) 的最大合法约数。
- 合法条件
- 子集非空 → 不能取 \(d=S\)(会导致一个子集和为 \(0\))
- 最优解:\(S\) 的最大真约数(即 \(S\) 除以其最小质因子)
解题思路
首先我们计算S的所有因数,S是n个数的总和,因为关键性质1,所以答案一定在S的因数中
然后我们枚举S的所有因数,对于每一个因数判断它是否能成为答案。
我们如何判断可以尽可能的快呢。
我们发现当我们从集合中选取一个数的时候,我能选取的范围是\((a,a+b-1)\)
选取两个数的范围为\((2a+1,2a+2b-3)\);
同样,所以我拿走n个集合中的数可组成的和的范围为前面n项的和到最后面n和之间;
只要这个区间内出现了一个我们当前枚举的因数或者他的倍数,这个因数就可以成为答案;
我的代码用了前缀和,如果用等差公式求和也是一样的;
pre[j] % it >= (pre[b] - pre[b - j]) % it || pre[b] - pre[j] - pre[b - j] >= it;
或者可以写成
(pre[b]-pre[j])/it-pre[j]/it>0;
答案就合法
完整 AC 代码(C++)
#include<iostream>
#include<string>
#include<algorithm>
#include <vector>
#include <cmath>
using namespace std;
using i64 = long long;
i64 pre[300005];
i64 a, b,t;
void solve() {
cin >> a >> b;
i64 sum = (a + a + b - 1) * b / 2;
vector<i64> cnt;
for (i64 i = 1; i * i <= sum; i++) {//记得i要开long long 不然会爆int
if (sum % i == 0)
{
cnt.push_back(i);
cnt.push_back(sum / i);
}
}
pre[0] = 0;
for (int i = 1; i <= b; i++) {
pre[i] = pre[i - 1] + i + a - 1;
}
sort(cnt.begin(), cnt.end());
i64 ans = 0;
for(auto it:cnt)
{
i64 v = 0;
for (int j = 1; j < b; j++) {
if (pre[j] % it >= (pre[b] - pre[b - j]) % it || pre[b] - pre[j] - pre[b - j] >= it) {
v = 1;
break;
}
}
if (v == 1) {
ans = max(ans, it);
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> t;
while(t--){
solve();
}
}

浙公网安备 33010602011771号