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;
}
}

浙公网安备 33010602011771号