【正难则反+素数】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;
}
posted @ 2022-10-21 14:18  Roshin  阅读(54)  评论(0编辑  收藏  举报
-->