Make It Divisible
Make It Divisible
gcd区间性质,st表
题意简述
给定一个长度为 (n) 的正整数序列 (a),以及一个整数 (k)。
求所有满足 (1 \le x \le k) 的整数 (x),使得序列 (a_1+x, a_2+x, \cdots, a_n+x) 是一个“可除序列”。
“可除序列”定义为:对于序列的任意子区间 ([l, r]),都存在一个数能被区间内所有数整除。
你需要输出满足条件的 (x) 的个数,以及所有满足条件的 (x) 之和。
核心性质推导
1. 区间合法的等价条件
区间内存在一个数能整除所有数 (\iff) 这个数必然是区间的最小值之一(因为能整除所有数,自然也能整除最小值)。
因此,一个子区间是“可除区间”的充要条件是:该区间的 (\gcd) 等于该区间的最小值。
2. 笛卡尔树思想(极大管辖区间)
如果暴力枚举所有 (O(n^2)) 个子区间去验证,必然超时。
注意到:给所有元素加上 (x) 后,元素之间的大小关系不会改变。
我们可以利用单调栈,求出每个元素 (a_i) 作为最小值的最大连续管辖区间 ([L_i, R_i])。
- 结论:只要所有的 (n) 个极大管辖区间都满足 (\gcd = \min),那么它们内部的所有更小子区间也必定满足。因此,我们只需要检查这 (n) 个极大管辖区间即可。
3. 差分性质与 (x) 的候选集(全局约束)
如果直接枚举 (x \in [1, k]),时间复杂度是 (O(k \cdot n)),无法接受。
根据 (\gcd) 的差分性质:(\gcd(a_1, a_2, \dots, a_n) = \gcd(a_1, |a_2-a_1|, \dots, |a_n-a_{n-1}|))。
当我们给原数组加上 (x) 后,差分数组是 ((a_{i+1}+x) - (a_i+x) = a_{i+1} - a_i),差分数组完全不变。
设原数组差分数组的全局 (\gcd) 为 (p)(固定值)。
设原数组的最小值为 (a_{min})。加上 (x) 后,新序列的最小值为 (a_{min} + x),其全局 (\gcd) 也必须等于 (a_{min} + x)。
因为全局 (\gcd) 必须能整除任意两数的差,所以 (a_{min} + x) 必须是 (p) 的约数。
即:(a_{min} + x \mid p)。
因此,合法的 (x) 必然满足 (x = d - a_{min}),其中 (d) 是 (p) 的约数。
由于 (p \le 10^9),它的约数个数最多只有 (1344) 个。我们直接把 (x) 的枚举量从 (10^9) 降到了至多 (1344) 个。
验证算法与复杂度
- 预处理全局差分 (\gcd) 为 (p)。
- 枚举 (p) 的所有约数 (d),如果 (1 \le d - a_{min} \le k),则作为候选 (x)。
- 用单调栈求出每个元素的极大管辖区间 ([L_i, R_i])。
- 用 ST表 预处理原数组的差分,以便 (O(1)) 查询区间差分 (\gcd)。对于每个管辖区间,预处理其差分 (\gcd) 为 (G_i)。
- 对于每个候选 (x),遍历所有极大管辖区间,检查是否满足:(a_i + x) 能整除 (G_i)。
即if (G[i] % (a[i] + x) != 0) 不合法;。
复杂度:预处理 ST 表和单调栈 (O(n \log n)),约数个数为 (f \le 1344)。验证复杂度 (O(f \cdot n))。总复杂度 (O(n \log n + f \cdot n)),由于 (\sum n \le 5 \times 10^4),可以轻松通过。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct SparseTable {
int n;
vector<int> lg;
vector<vector<ll>> st;
SparseTable(const vector<ll>& d) {
n = d.size() - 1;
if(n <= 0) return;
lg.assign(n + 1, 0);
for(int i = 2; i <= n; i ++) lg[i] = lg[i / 2] + 1;
int K = lg[n] + 1;
st.assign(n + 1, vector<ll>(K, 0));
for(int i = 1; i <= n; i ++) st[i][0] = d[i];
for(int j = 1; j < K; j ++) {
for(int i = 1; i + (1 << j) - 1 <= n; i ++) {
st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
ll query(int L, int R) {
if(L > R || n == 0) return 0;
int j = lg[R - L + 1];
return gcd(st[L][j], st[R - (1 << j) + 1][j]);
}
};
void solve() {
int n; ll k; cin >> n >> k;
vector<ll> a(n + 1), d(n + 1);
ll mn = 1e10;
//找出amin,预处理差分数组
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) mn = min(a[i], mn);
for(int i = 1; i < n; i ++) d[i] = abs(a[i + 1] - a[i]);
if(n == 1) {
cout << k << " " << (ll)k * (k + 1) / 2 << "\n"; return;
}
//建st表
SparseTable st(d);
ll p = st.query(1, n - 1);
if(p == 0) {
cout << k << " " << (ll)k * (k + 1) / 2 << "\n"; return;
}
//找出符合条件的x,必定为全局gcd p的约数
vector<ll> div;
for(ll i = 1; i * i <= p; i ++) {
if(p % i == 0) {
ll j = p / i;
if(i > mn && i - mn <= k) div.push_back(i - mn);
if(j > mn && j - mn <= k) div.push_back(j - mn);
}
}
sort(div.begin(), div.end());
div.erase(unique(div.begin(), div.end()), div.end());
//利用单调栈,找出a[i]的影响区间(在区间内,a[i]为最小值)
vector<int> L(n + 1, 1), R(n + 2, n);
stack<int> stk;
for(int i = 1; i <= n; i ++) {
while(!stk.empty() && a[stk.top()] >= a[i]) stk.pop();
L[i] = (stk.empty() ? 1 : stk.top() + 1);
stk.push(i);
}
while(!stk.empty()) stk.pop();
for(int i = n; i >= 1; i --) {
while(!stk.empty() && a[stk.top()] >= a[i]) stk.pop();
R[i] = (stk.empty() ? n : stk.top() - 1);
stk.push(i);
}
//预处理区间差分gcd,区间缩小,gcd必然成倍增加,必然符合,所以只需整个影响区间符合即可
vector<ll> dif(n + 1);
for(int i = 1; i <= n; i ++) {
if(R[i] == L[i]) continue;
dif[i] = st.query(L[i], R[i] - 1);
}
ll ans = 0, tol = 0;
for(auto x : div) {
bool flag = true;
for(int i = 1; i <= n; i ++) {
if(R[i] == L[i]) continue;
if(dif[i] > 0 && dif[i] % (a[i] + x) != 0) {
flag = false; break;
}
}
if(flag) {
ans ++; tol += x;
}
}
cout << ans << " " << tol << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int _ = 1; cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号