题解:[CF609E/代码源OJ L4上 倍增 - 982] cut
感谢@Cute_SilverWolf为这篇题解作出的贡献。
我不管!我就要写DS!
——第一眼看到这道题之后,Hsl_Beat的发言
这道题的倍增好写多了——至少是比今天凌晨写的代码源OJ-981的好写不少。
题目描述
这一次,Baby Ehab 只会剪纸而不会粘贴。他开始时有一张纸,上面写着一个长度为 的数组 ,然后他会进行如下操作:
- 他选择一个区间 ,将子段 剪下来,移除数组的其他部分。
- 然后,他将这个区间再切分成若干个子区间。
- 为了增加一点数论的趣味,他要求每个子区间内所有元素的乘积必须等于它们的最小公倍数(LCM)。
形式化地说,他需要将 划分为若干个连续的子数组,使得每个子数组的所有元素的乘积等于它们的最小公倍数。现在,对于 个独立的区间 ,请你告诉 Baby Ehab 至少需要将该区间划分为多少个这样的子数组。
,
思路
我们先来思考这样一个问题:如何求出以位置为左节点,另一个满足条件的右节点位置的下一格最远在哪里?
首先不难想到的是,这个位置不能超过所对应的位置。
其次把分解质因数,对于每个分解出来的质数,选择的位置不能超过它上一次出现的位置。
可以用埃筛预处理可能出现的每个数的质因数,这里最多到1e5,完全能跑,接着把数组倒着遍历一遍就可以。
然后可以倍增,表示结点跳步会到哪一个结点,那应该就是,相当于跳了两次步,也就是步。
现在问题来到了怎么查询之间的答案,可以直接从大道小枚举二进制的位数,要是当前的跳步不会超过,那么我们就更新到并把答案加上。
然后就不知道写什么了。
代码
using namespace std;
vector<int> d[100005];
int f[100005][30];
int pos[100005];
signed main()
{
d[1].push_back(1);
for (int i = 2; i <= 100000; i++) {
if (d[i].empty()) {
for (int j = 1; i * j <= 100000; j++) {
d[i * j].push_back(i);
}
}
}
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
f[n][0] = n;
memset(pos, 0x3f3f3f3f, sizeof(pos));
for (int i = n - 1; i >= 0; i--) {
f[i][0] = f[i + 1][0];
for (int j = 0; j < d[a[i]].size(); j++) {
f[i][0] = min(f[i][0], pos[d[a[i]][j]]);
pos[d[a[i]][j]] = i;
}
}
for (int i = 1; i < 20; i++) {
for (int j = 0; j <= n; j++) {
f[j][i] = f[f[j][i - 1]][i - 1];
}
}
while (q--) {
int l, r;
cin >> l >> r;
l--;
r--;
int ans = 0;
for (int i = 19; i >= 0; i--) {
if (f[l][i] <= r) {
l = f[l][i];
ans += (1 << i);
}
}
cout << ans + 1 << '\n';
}
return 0;
}
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments