cf1516 D. Cut

题意:

给定数组。每次询问 \(l,r\),求至少把 \([l,r]\) 分成几段才能使每段都是好段。

好段:段中所有数的积等于 LCM。

\(1\le a_i\le 1e5\)

思路:

显然好段当且仅当每个素因子仅为一个数所有。

刚开始想用区间搞,发现写不出来。

正解是倍增:\(f[i][j]=x\) 表示从 \(i\) 往右跳 \(2^j\) 次到达位置 \(x\)

每次是怎么跳的呢?跳到以 \(i\) 为左端点的最长好段的下一个位置。

每次跳多长是不定的,反正尽量跳远一点。

首先计算 \(f[i][0]\):对每个数用根号时间分解得到质因子,取上次出现位置最左的质因子。最后要取前缀 min

然后递推求 \(f[i][j\ge 1]\)

对每次询问,从大到小跳。

const signed N = 3 + 1e5;
int n, q, a[N], f[N][19];
void sol() {
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    memset(f, INF, sizeof f);
    vector<int> pos(N, n+1); //记录每个质因子最后出现的位置

    //算f[i][0]
    for(int i = n; i; i--) {
        int t = a[i];
        for(int p = 2; p <= a[i] / p; p++) if(t % p == 0) {
            f[i][0] = min(f[i][0], pos[p]), pos[p] = i;
            while(t % p == 0) t /= p;
        }
        if(t > 1) f[i][0] = min(f[i][0], pos[t]), pos[t] = i;
    }
    for(int i = n-1; i; i--) f[i][0] = min(f[i][0], f[i+1][0]); //前缀min

    //递推f[i][j],注意下标别越界
    for(int j = 1; j <= 17; j++)
        for(int i = 1; f[i][j-1] < n; i++)
            f[i][j] = f[f[i][j-1]][j-1];

    //倍增处理询问
    while(q--) {
        int l, r, ans = 0; cin >> l >> r;
        for(int i = 17; i >= 0; i--) //右边的位置起跳,能跳的步数肯定比左边少
            if(f[l][i] <= r) ans += (1<<i), l = f[l][i];
        cout << ans + 1 << endl;
    }
}
posted @ 2022-06-02 15:36  Bellala  阅读(78)  评论(0)    收藏  举报