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) 个。

验证算法与复杂度

  1. 预处理全局差分 (\gcd) 为 (p)。
  2. 枚举 (p) 的所有约数 (d),如果 (1 \le d - a_{min} \le k),则作为候选 (x)。
  3. 用单调栈求出每个元素的极大管辖区间 ([L_i, R_i])。
  4. 用 ST表 预处理原数组的差分,以便 (O(1)) 查询区间差分 (\gcd)。对于每个管辖区间,预处理其差分 (\gcd) 为 (G_i)。
  5. 对于每个候选 (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;
}
posted @ 2026-11-03 21:24  thenrify  阅读(2)  评论(0)    收藏  举报