CF1632 D. New Year Concert

题意:

给定数组。对一个区间 \([l,r]\),若其中所有数的 gcd 恰等于区间长度,则这个区间是 bad 的;否则是 good 的。

每次操作可以修改数组中的一个数,改为任意整数。

定义 \(f(i)\) 为使 \([1,i]\) 的所有子区间都 good 的最小操作数。求 \(f(1),f(2),\cdots ,f(n)\)

思路:

首先找出所有 bad 区间:

对于固定的 \(l\),随着 \(r\) 增加,\(\gcd(a_l,a_{l+1},\cdots ,a_r)\) 递减,区间长度 \(r-l+1\) 递增。所以可以二分+ST表求出所有 bad 区间。(听说也可以双指针、线段树之类的)

怎样把一个 bad 区间改成 good 的?只需把其中的任一数改成一个超大的质数。假如改了 \(a_i\),那么所有包含 \(a_i\) 的区间都将 good

然后变成一个经典贪心:

选择最少的点,使每个区间至少包含一个点。按右端点排序然后每次贪心选右端点即可。

(还可以发现这些区间是不会互相包含的,但不知道好像也没关系)

int erfen(int x) { //找 gcd(a[x],a[x+1],...,a[y]) >= y-x+1 的最大y
    int l = x, r = n;
    while(l < r) {
        int mid = (ll)l + r + 1 >> 1;
        if(ask(x,mid) >= mid-x+1) l = mid; else r = mid-1;
    }
    return l;
}

signed main() {
    iofast;
    cin >> n; for(int i = 1; i <= n; i++) cin >> a[i];

    initST();

    vector<PII> segs; //[r,l]
    for(int l = 1; l <= n; l++) { //找出所有的bad区间
        int r = erfen(l);
        if(ask(l,r) == r-l+1) segs.pb({r,l});
    }

    vector<int> ans(n+1,0);
    sort(all(segs));
    int now = 0; for(auto &[r,l]: segs)
        if(l > now) ++ans[r], now = r; //未被覆盖
    for(int i = 1; i <= n; i++) //取前缀和
        ans[i] += ans[i-1], cout << ans[i] << ' ';
}
posted @ 2022-05-15 20:49  Bellala  阅读(43)  评论(0)    收藏  举报