D. Cut(质因子分解,倍增)
D. Cut
题意:给出一个长度为n的序列,和q个询问,对于每个询问,求出最少分成多少个连续的子区间,使每个子区间的 l c m lcm lcm等于子区间各个数的乘积。
思路:质因子分解+倍增思想。
-
预处理:利用质因子分解来找当前元素能够向后跳到的最远的位置(满足从开始位置到结束位置中的每个元素都互质)
-
利用倍增的思想,来加速跳的过程。
-
对于每个询问,只要从没有跳到边界,就一直往下跳,直到位置大于右边界,跳的次数加一就时答案。
预处理: d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示区间 [ i , d p [ i ] [ 0 ] ] [i,dp[i][0]] [i,dp[i][0]]中所有的元素都是互质的。
倍增: d p [ i ] [ j ] dp[i][j] dp[i][j]表示从 i i i开始跳 2 j 2^j 2j次,跳到的位置(注意每次跳的长度不一定是相同的),也就是分成了 2 j 2^j 2j个区间。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int s[N], ne[N], dp[N][20];
unordered_map<int, int> p;
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
memset(ne, 0x7f, sizeof ne);
int n, m, x, y;
cin >> n >> m;
for(int i=1; i<=n; i++) {
cin >> s[i];
}
//预处理
dp[n+1][0] = n+1;
for(int i=n; i>=1; i--) {
int tmp = s[i];
dp[i][0] = dp[i+1][0]; //继承前一个元素能到到最大位置。
for(int j=2; 1ll*j*j<=tmp; j++) {//质因子分解
if(tmp % j == 0) {
if(p[j]) ne[i] = min(p[j], ne[i]);//判断之前是否出现过这个素数。
p[j] = i;//标记
while(tmp % j == 0) tmp /= j;
}
}
if(tmp > 1) {
if(p[tmp]) ne[i] = min(p[tmp], ne[i]);
p[tmp] = i;
}
if(ne[i] == 2139062143) ne[i] = n+1;
dp[i][0] = min(ne[i], dp[i][0]);//取最小值,为了使从开始位置到结束位置中的每个元素都互质
}
for(int i=n+1; i>=1; i--) {
for(int j=1; j<=17; j++) {//倍增思想。
dp[i][j] = dp[dp[i][j-1]][j-1];
}
}
//每个询问
for(int i=1; i<=m; i++) {
cin >> x >> y;
int ans = 0;
for(int i=17; i>=0; i--) {//可以证明,下一个位置能够跳的次数一定小于之前跳的次数。
if(dp[x][i] <= y) {
ans += (1<<i);
x = dp[x][i];
}
}
cout << ans+1 << endl;
}
return 0;
}