题目大意:
给出一个序列 \(a\)。
对于一个序列 \(b\),如果其中存在一个区间 \([L, R]\),满足区间 \(gcd\) 等于区间长度 \(R - L + 1\),则认为这个序列是不好的。
每次修改可以将任意一个数改成任意正整数。
现求对序列 \(a\) 的每一个前缀最少需要修改多少次,使得该前缀是一个好序列。
思路:
通过简单模拟一下样例 \(3\),我们可以发现。
- \(f(x)\) 的状态继承了 \(f(x - 1)\) 的状态。并且假设每次答案最多增加 \(1\)(但还是不太懂为什么)
- 对于一个区间 \([l , r]\),其区间 \(gcd\) 满足单调递增。
- 为了让修改次数最少,我们可以每次将数修改为一个大质数,因为区间 \(gcd\) 单调递增,那么修改成大质数的话,我们就不用考虑这个质数所在位置及其前面的数字了,他们与区间右端点的 \(gcd\) 都为 \(1\),而要想满足是不好序列的条件只用检查区间端点。
由于我们并不需要真的去修改一个数,我们可以用 ST 表预处理出区间 \(gcd\) 。
那么,现在问题就在于当我们考虑以 \(i\) 位置为结尾的前缀时,我们如何求出 \([1, i]\) 这段区间上我们要修改几次。
前面说到 \(f(x)\) 的状态可以由 \(f(x - 1)\) 的状态转移得到,并且我们在 \(x\) 之前可能会进行一些修改操作,将某些数字改成大质数,那么我们可以用变量 \(idx\) 记录上一次修改的位置,问题就由转化成如何求出在 \([idx, i]\) 这段区间上我们要修改几次。
由单调性考虑二分,二分找是否存在一个位置 \(p\) ,使得 \([p, i]\) 是一个不合法区间。
令 \(tmp\) 为区间 \([mid, i]\) 的 \(gcd\),\(len\) 为 \([mid, i]\) 的区间长度。
因为 \(tmp\) 单调递增,\(len\) 单调递减,那么一定会出现以下的局面。
----------------|*|------------------*>
tmp < len tmp > len i
不合法
这样我们就能二分的找是否存在这个位置 \(p\)。
Code:
class SparseTable {
public:
int lg[N] = {-1};
ll st[24][N];
template <class T>
T op(T &a, T &b) { return gcd(a, b); } //检查区间操作!
SparseTable() {
for (int i = 1; i < N; i++) {
lg[i] = lg[i / 2] + 1;
}
}
inline void init(int n, vector<ll> &a) {
//完成初始化! for i in [1, n]: st[0][i] = val[i], 对应区间 [i, i + 2^0 -1]
for (int i = 1, x; i <= n; i++) {
st[0][i] = a[i];
}
build(n);
}
inline void build(int n) {
for (int i = 1; i <= lg[n]; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++)
st[i][j] = op(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
}
ll query(int l, int r) {
ll len = lg[r - l + 1];
return op(st[len][l], st[len][r - (1 << len) + 1]);
}
};
SparseTable ST;
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
ST.init(n, a);
ll ans = 0;
int idx = 1; // 将a[idx - 1]修改为大质数
for (int i = 1; i <= n; i++) {
if (a[i] == 1) {
ans++;
idx = i + 1;
} else {
int l = idx, r = i; // 二分找是否存在一个位置p,使得[p, i]是一个bored区间
bool cur = false;
while (l <= r) {
int mid = (l + r) >> 1;
int tmp = ST.query(mid, i);
int len = i - mid + 1;
if (tmp < len) {
l = mid + 1;
} else if (tmp == len) {
cur = true;
break;
} else {
r = mid - 1;
}
}
if (cur) {
ans++;
idx = i + 1;
}
}
cout << ans << " \n"[i == n];
}
return 0;
}