CodeForces - 1632D New Year Concert(ST 表、二分)

D - New Year Concert

题目大意:

给出一个序列 \(a\)

对于一个序列 \(b\),如果其中存在一个区间 \([L, R]\),满足区间 \(gcd\) 等于区间长度 \(R - L + 1\),则认为这个序列是不好的。

每次修改可以将任意一个数改成任意正整数。

现求对序列 \(a\) 的每一个前缀最少需要修改多少次,使得该前缀是一个好序列。

思路:

通过简单模拟一下样例 \(3\),我们可以发现。

  • \(f(x)\) 的状态继承了 \(f(x - 1)\) 的状态。并且假设每次答案最多增加 \(1\)(但还是不太懂为什么)
  • 对于一个区间 \([l , r]\),其区间 \(gcd\) 满足单调递增。
  • 为了让修改次数最少,我们可以每次将数修改为一个大质数,因为区间 \(gcd\) 单调递增,那么修改成大质数的话,我们就不用考虑这个质数所在位置及其前面的数字了,他们与区间右端点的 \(gcd\) 都为 \(1\),而要想满足是不好序列的条件只用检查区间端点。

由于我们并不需要真的去修改一个数,我们可以用 ST 表预处理出区间 \(gcd\)

那么,现在问题就在于当我们考虑以 \(i\) 位置为结尾的前缀时,我们如何求出 \([1, i]\) 这段区间上我们要修改几次。

前面说到 \(f(x)\) 的状态可以由 \(f(x - 1)\) 的状态转移得到,并且我们在 \(x\) 之前可能会进行一些修改操作,将某些数字改成大质数,那么我们可以用变量 \(idx\) 记录上一次修改的位置,问题就由转化成如何求出在 \([idx, i]\) 这段区间上我们要修改几次。

由单调性考虑二分,二分找是否存在一个位置 \(p\) ,使得 \([p, i]\) 是一个不合法区间。

\(tmp\) 为区间 \([mid, i]\)\(gcd\)\(len\)\([mid, i]\) 的区间长度。

因为 \(tmp\) 单调递增,\(len\) 单调递减,那么一定会出现以下的局面。

----------------|*|------------------*>
    tmp < len          tmp > len     i
                不合法

这样我们就能二分的找是否存在这个位置 \(p\)

Code:
class SparseTable {
public:
    int lg[N] = {-1};
    ll st[24][N];
    template <class T>
    T op(T &a, T &b) { return gcd(a, b); } //检查区间操作!
    SparseTable() {
        for (int i = 1; i < N; i++) {
            lg[i] = lg[i / 2] + 1;
        }
    }
    inline void init(int n, vector<ll> &a) {
        //完成初始化! for i in [1, n]: st[0][i] = val[i], 对应区间 [i, i + 2^0 -1]
        for (int i = 1, x; i <= n; i++) {
            st[0][i] = a[i];
        }
        build(n);
    }
    inline void build(int n) {
        for (int i = 1; i <= lg[n]; i++)
            for (int j = 1; j + (1 << i) - 1 <= n; j++) 
                st[i][j] = op(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
    }
    ll query(int l, int r) {
        ll len = lg[r - l + 1];
        return op(st[len][l], st[len][r - (1 << len) + 1]);
    }
};
 
SparseTable ST;
 
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
 
    ST.init(n, a);
 
    ll ans = 0;
    int idx = 1; // 将a[idx - 1]修改为大质数
 
    for (int i = 1; i <= n; i++) {
        if (a[i] == 1) {
            ans++;
            idx = i + 1;
        } else {
            int l = idx, r = i; // 二分找是否存在一个位置p,使得[p, i]是一个bored区间
            bool cur = false;
            while (l <= r) {
                int mid = (l + r) >> 1;
                int tmp = ST.query(mid, i);
                int len = i - mid + 1;
                if (tmp < len) {
                    l = mid + 1;
                } else if (tmp == len) {
                    cur = true;
                    break;
                } else {
                    r = mid - 1;
                }
            }
            if (cur) {
                ans++;
                idx = i + 1;
            }
        }
        cout << ans << " \n"[i == n];
    }
    return 0;
}
posted @ 2022-03-05 19:46  Nepenthe8  阅读(68)  评论(0编辑  收藏  举报