【正难则反+素数】CF1749D. Couting Arrays
CF1749D. Counting Array
赛时1A过了就简单写一下题解吧。
题意
题目定义了合法删除操作,\(gcd(a_i,i)=1\) 则可以删除 i 下标的元素,然后后面的元素依次左移。
定义 ambiguous arrays 为删除序列不唯一的数组。求长度为 \([1,n]\) 的所有数组中,ambiguous arrays 的数量。
数据范围:\(2\leq n \leq 3\times 10^5, 1\leq m \leq 10^{12}\)
思路
- 首先观察到删除序列唯一的数组都有什么性质,由于 \(gcd(a_i,i)=1\) 才可以删除,所以 \([1,1,...,1]\) 这样的删除序列一定是存在的。
- 如果序列不唯一则代表至少有一次操作是可以删除 \(a_1\) 以外的元素的。
- 发现正着很难做,考虑正难则反和补集,数组总数等于 \(tot=m^1 +m^2+\cdots + m^n\) ,我们只需要求出删除序列只能是全 \(1\) 的数组个数即可。
- 先从样例开始模拟,考虑长度为 1 的数组,发现都合法。考虑长度为 2 的数组,发现只要 \(gcd(a_2, 2) \neq 1\) 就合法,对于长度为 \(2\) 的合法数组个数为 \(m\times \frac{m}{2}\) 。
- 依次再考虑长度为 3 的数组,你手模一下就能发现,如果前 2 个元素组成的数组非法,由此延伸的数组也一定非法。所以只用考虑长度为 2 的合法数组的延伸。
- 则对于长度为 3 的数组,合法答案为 \(m\times \frac{m}{2} \times \frac{m}{6}\) 。
- 由 \(gcd\) 的性质你可以发现:
- 如果 \(i\) 是合数,在长度为 \(i-1\) 的合法数组末尾的候选数 \(a_{i-1}\) 放在 \(i\) 依然是合法的。
- 而如果 \(i\) 是素数,你需要对 \(a_{i-1}\) 的候选数进行再一次筛选,即 \(i\) 必须是 \(a_{i-1}\) 候选数的因子。
- 因此答案显然,对于长度为 \(i\) 的数组,考虑每一个下标 \(j\leq i\) 有多少个候选数。总答案为:
-
\[ans=tot-\sum_{i=1}^n \prod_{j=1}^i \lfloor \frac{m}{Product_j} \rfloor \]
- 其中 \(Product_j\) 表示 \([1,j]\) 的所有素数乘积。因此在长度打到较小的某个值时就不会存在合法数组了。
-
- 时间复杂度 \(O(n)\) ,实现时注意 \(m\) 的范围,爆 longlong 要开 int128 。
Code
const int mod = 998244353, N = 5e5 + 10;
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
int main() {
get_primes(N - 10);
ll n, m;
re(n), re(m);
ll tot = 0;
__int128 v = m;
for (int i = 0; i < n; i++) {
tot = (tot + v) % mod;
v = v * m % mod;
}
__int128 sub = m, now = m, rem = m, pre = 1;
for (int i = 2; i <= n && rem; i++) {
if (st[i]) {
now = now * rem % mod;
sub = (sub + now) % mod;
}
else {
pre = pre * i;
rem = m / pre;
now = now * rem % mod;
sub = (sub + now) % mod;
}
}
ll ans = (tot - sub + mod) % mod;
printf("%lld\n", ans);
return 0;
}