可划分数组
可划分数组
1. 基本定义
对数组中的每个位置 i:
L[i]:左边最近的与a[i]不互质的位置,若不存在则为0。R[i]:右边最近的与a[i]不互质的位置,若不存在则为n+1。
两个数不互质 ⇔
gcd(a[i], a[j]) > 1。
2. 合法区间的判定(核心)
对于一个区间 [l, r],它是合法的,当且仅当区间内每一个位置 x 都能在区间内找到另一个位置 y(y ≠ x)使得 gcd(a[x], a[y]) > 1。
用 L[x] 和 R[x] 表示,位置 x 在区间 [l, r] 内找不到伙伴,当且仅当:
L[x] < l(左边最近的不互质位置在区间外)- 且
R[x] > r(右边最近的不互质位置也在区间外)
因此,区间 [l, r] 合法 ⇔ 不存在 位置 x ∈ [l, r] 同时满足 L[x] < l 和 R[x] > r。
等价于:对任意 x ∈ [l, r],若 R[x] > r,则必须 L[x] ≥ l。
3. 固定右端点 r 的扫描思路
我们固定右端点 r,希望找到所有合法的左端点 l(1 ≤ l ≤ r-1,因为区间长度至少为 2)。
定义:
mxleft= 所有满足x ∈ [l, r]且R[x] > r的位置x的L[x]的最小值。
则区间 [l, r] 合法 ⇔ mxleft ≥ l。
4. DP 转移
令 dp[i] 表示前 i 个元素能划分出的最大段数,dp[0] = 0,其它初始化为 -∞。
对于每个右端点 r(从 2 到 n),我们向左枚举左端点 l(从 r-1 到 1),并维护 mxleft 的值:
- 初始化
mxleft = L[r](只考虑位置r自己)。 - 当我们将左边界从
l+1扩展到l时,新加入的位置是l,如果R[l] > r,则l的右侧没有伙伴,需要更新mxleft = min(mxleft, L[l])。 - 此时
mxleft正好是区间[l, r]中所有右侧无伙伴位置的L最小值。 - 若
mxleft ≥ l,则区间[l, r]合法,此时可用dp[l-1] + 1更新dp[r]。
转移方程:dp[r] = max(dp[r], dp[l-1] + 1) (对于所有满足 mxleft ≥ l 的 l)
最终答案即 dp[n],若 dp[n] < 0 则输出 -1。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = 1e9;
void solve() {
int n; cin >> n;
if(n == 1) {cout << "-1\n"; return;}
vector<ll> a(n + 1);
for(int i = 1; i <= n; i ++) cin >> a[i];
//预处理左右最近的合法数
vector<int> L(n + 1, 0), R(n + 1, n + 1);
for(int i = 1; i <= n; i ++) {
ll k = a[i];
int l = i - 1, r = i + 1;
while(l >= 1 && gcd(k, a[l]) == 1) l --;
while(r <= n && gcd(k, a[r]) == 1) r ++;
L[i] = l, R[i] = r;
}
vector<int> dp(n + 1, -INF); dp[0] = 0;
for(int i = 2; i <= n; i ++) {
int mxleft = L[i];
vector<int> pos;
for(int j = i - 1; j >= 0; j --) {
if(R[j] <= i) {
//统计所有合法的左端点
if(mxleft >= j) pos.push_back(j);
}
else mxleft = min(mxleft, L[j]);
}
for(auto& left : pos) {
if(left == 0) continue;
dp[i] = max(dp[i], dp[left - 1] + 1);
}
}
cout << (dp[n] <= 0 ? -1 : dp[n]) << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int _=1; //cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号