前言

赛时一直在做这题但是没推出来呜呜,下次还是不能懒,多打表。一些东西也不要随便化简,可能化简之后反而看不出结论了。

感谢 ciuim 和 NaCly_Fish 等大神在 LA 群教会我做这题。

题目翻译

给定 $n$,求 $(\Large\sum\limits_{j=1}^n\sum\limits_{i=j}^n(C(i,j)\bmod j))\bmod p$。

其中 $p=10^9+7$。

$C(i,j)$ 表示圆排列,即 $\large\binom{i}{j}(j-1)!$。

$n!$ 表示阶乘,即 $n\times (n-1)\times (n-2)\times \dots\times 1$。

$t$ 组询问。

$1\leq t\leq 5\times 10^5,1\leq n\leq 10^6$。与大部分 CF 类型题不一样的是,本题没有对 $\sum n$ 的约束

题目思路

首先先把题目这个鬼式子打开来。即为

$$
(\Large\sum\limits_{j=1}^n\sum\limits_{i=j}^n(\dfrac{i!(j-1)!}{j!(i-j)!}\bmod j))\bmod p
$$

这时容易发现,对于每个 $j$ 分开处理比较好,因为都带一个 $\bmod j$。

然后看到这个时间限制,不难猜测是不劣于 $\mathcal O(n\log n)$ 的解法。

当然虽然没有保证 $\sum n$ 但是我们可以提前把 $[1,10^6]$ 的答案都算出来然后 $\mathcal O(1)$ 回答。

那么我们大概猜测这样一个东西:对于任意 $j$ 在均摊不劣于 $\mathcal O(\log n)$ 时间求出 $\Large(\sum\limits_{i=j}^n(\dfrac{i!(j-1)!}{j!(i-j)!}\bmod j))\bmod p$。

考虑到这个 $\bmod j$ 的循环节什么的,很容易往调和级数上去想。事实上我赛时也推到了这一步。

这个时候集中注意力观察。考虑到这个式子是一个二项式系数乘上一个 $(j-1)!$。那么 $(j-1)! \equiv 0\pmod j$ 是上式为 $0$ 的充分条件。

接下来把注意力再次放大,发现不满足 $j=4\operatorname{or}j\text{ is prime}$ 的 $j$ 贡献都是 $0$。

不满足 $j\text{ is prime}$ 的 $j$ 贡献都是 $0$,这个结论如何证明?

首先 $j=1$ 显然任何数 $\equiv 0\pmod 1$。

然而当 $j$ 是合数且 $j\neq 4$ 的时候,$(j-1)!\equiv 0\pmod j$ 的证明如下。

考虑合数书写是 $a\times b$ 满足 $a\neq 1\operatorname{and} b\neq 1$,那么朴素情况下即 $a\neq b$,显然 $a$ 与 $b$ 分别作为一个因子在 $(j-1)!$ 中出现了。

极端一点,$a=b$,且 $a,b$ 均为质数,这是最不好处理的。

然而,由于对于 $a\gt2$ 的情况,一定可以被证明成 $2a$ 与 $b$ 也分别作为一个因子在 $(j-1)!$ 中出现了。

$a=b=2$ 是最特殊的情况,因此 $4$ 不被包括在这类合数中。


这里有个问题我想了很久,感觉是熬夜打 CF 然后熬夜补题导致的脑子生锈。我们并不是说满足 $(j-1)! \equiv 0\pmod j$ 的才可以满足上式为 $0$。只是说满足 $(j-1)! \equiv 0\pmod j$ 一定满足上式为 $0$,而且可以筛去大部分无用的数。操作起来很方便。


接下来有了这个结论就很好做了。

之后的推导需要『威尔逊定理』和『卢卡斯定理』作为前置知识。

威尔逊定理:对于任意质数 $p$ 满足 $(p-1)!\equiv p-1 \pmod p$。

卢卡斯定理:对于任意质数 $p$ 满足 $\large \binom n m\bmod p=\binom{\lfloor n/p\rfloor}{\lfloor n/m\rfloor}\times \binom{n\bmod p}{m\bmod p}\bmod p$。

$j=4$ 部分很简单,直接按上式算就可以,$\mathcal O(n)$ 解决。乘法过程会炸 long long,可以使用 __int128 解决。

$j\text { is prime}$ 部分,把上面两个定理组合起来,得到 $\Large\binom ij(j-1)!\equiv \binom ij(j-1)\equiv -\binom i j\equiv-\lfloor\frac i j\rfloor\pmod p$。

因此,$[kj,(k+1)j-1]$ 之间产生的贡献是一样的,都是 $-\lfloor\frac i j\rfloor=-k$。

所以我们直接枚举这个 $k$ 即可。区间加法可以使用差分解决。

复杂度是一个类似调和级数的东西。当然,这里只有质数,所以复杂度等同埃筛即 $\mathcal O(n\log \log n)$。

注意做完之后需要再做一次前缀和才能 $\mathcal O(1)$ 回答。

部分代码

https://codeforces.com/contest/1957/submission/257645847

省略了 modint 部分。

注意做完之后需要再做一次前缀和才能 $\mathcal O(1)$ 回答。

所以代码最后出现了 2 次前缀和。第一次是差分操作,第二次是前缀和。

#include <bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
using Z = mod_int<P>;
typedef long long ll;
typedef __int128 LL;
const int N = 1e6;
int p[1000020];
bool np[1000020];
Z ans[1000020];
void add(int l, int r, Z w)
{
    ans[l] += w, ans[min(r, N) + 1] -= w;
}
void solve()
{
    int n;
    cin >> n;
    cout << ans[n] << '\n';
}
int main()
{
    np[1] = 1;
    for (int i = 2; i <= N; i++)
    {
        if (!np[i])
            p[++p[0]] = i;
        for (int j = 1; j <= p[0] && i * p[j] <= N; j++)
        {
            np[i * p[j]] = 1;
            if (i % p[j] == 0)
                break;
        }
    }
    for (int j = 1; j <= N; j++)
    {
        if (np[j])
        {
            if (j == 4)
            {
                for (LL k = 4; k <= N; k++)
                    add(k, k, k * (k - 1) * (k - 2) * (k - 3) / 4 / 3 / 2 / 1 * 3 * 2 * 1 % 4);
            }
            continue;
        }
        for (int k = 1; k * j <= N; k++)
            add(k * j, (k + 1) * j - 1, (-k % j + j) % j);
    }
    for (int i = 1; i <= N; i++)
        ans[i] += ans[i - 1];
    for (int i = 1; i <= N; i++)
        ans[i] += ans[i - 1];
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}
最后修改:2024 年 04 月 22 日
v我50吃疯狂星期四