[HZOI] 弹药科技

题目描述

经过精灵族全力抵挡,精灵终于坚持到了联络系统的重建,于是精灵向人类求助,
大魔法师伊扎洛决定弓}用博士的最新科技来抗敌。
伊扎洛:“博士,还没好吗?”
博士:“只差一步了!只需要在正确的位置装上弹药就可以了!”
博士的最新科技是全新的炸弹,但是现在还需要一步装弹药的操作。博士的炸弹有
N!个位置可以装弹药(>.<),但是只有在正确的位置装上弹药才能启动,博士将
装弹药的位置编号为1到N!,一个位置i需要装弹药,当且仅当gcd(i, N!) ≠ 1且
N! mod i ≠ 0,现在博士不要求你求出所有装弹药位置,只需要你求出满足要求
的位置的数量就可以了。

数据范围

N <= 1000000

————————————分界线——————————————

题解

首先当我们看到这个数据范围的时候
N <= 1000000
再看到要求算n!
心里肯定是无数只草泥马跑过了吧
—————————————进入正题——————————————
放导弹的位置满足
gcd(i, N!) ≠ 1且 N! mod i ≠ 0
从这里面我们便可以推出
=>i 为 与 n! 不互质的数 and i不是n! 的因子
而我们要求的这个答案即n! - ans1 - ans2 + 1
(ans1 代表与n! 互质的数的个数,ans2为因子数,同时1被重复计算了,所以需要-1)

我们进行分部解决,将ans1与ans2分步解决

因子数

根据唯一分解定律, 任意数t = p1 ^ a1 + p2 ^ a2 + … + pn ^ an
t的因数个数为ans = Π (ai + 1)
根据Legendre定理
在这里插入图片描述
证明
在这里插入图片描述
使用这个定理,我们就可以找出a[i]了,当然,应该先Euler筛法走一波

    for (int i = 1;i <= cnt; i++)
    {
        k = 1;
        ///一定记得将k清1啊
        ///在正数n!的素因子标准分解式中, 素数p的最高指数记作pn[n!]
        ///则pn[n!] = Σ(k >= 1) ceil(n / p ^ k)
        for (int j = 1;k * prime[i] <= n; j++)
            k *= prime[i] ,pn[i] += n / k;
        ///第j时即为prime[i] ^ j
    }

然后我们就可以使用我们的唯一分解定理将n!的因子数筛出来
只需要枚举每一个质数即可

for (int i = 1;i <= cnt; i++)
    ans2 = ans2 * (pn[i] + 1) % mod;
    ///性质
    ///根据唯一分解定律, 任意数t = p1^a1 + p2^a2 + ... + pi^ai
    ///t的因数个数为ans = Π (ai + 1)

与 n! 互质的数

回想一下,与n互质的数,不就是Euler函数φ(n)吗
那么n!的φ应该如何求呢
这个时候
我们需要调用Euler函数的另一种求法(单纯求一个)

int phi (int n)
{
    int res = n;
    for (int i = 2;i * i <= n; i++)
        if (n % i == 0)
        {
            res = res / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    if (n > 1)
        res = res / n * (n - 1);
    return res;
}

在本题中我们需要求的是n!的phi
那我们就可以在每一次/i的时候,不进行除法操作(* i / i = 1)
所以便有了

	for (int i = 2;i <= n; i++)
         if (!vis[i])
             ans1 = ans1 * (i - 1) % mod;
         else
             ans1 = ans1 * i % mod;
    ///原本应是   if (!vis[i])
    ///              ans1 = ans1 * (i - 1) / i % mod;
    ///           else
    ///              ans1 = ans1 * i / i = ans1;
    ///而此题中, 应为求的是阶乘, 故自带一个 * i
    ///套取公式
    ///我们便可以把它消掉
    ///然后就ok了

如此,我们就求出了ans1和ans2的值
最后
用n! - ans1 - ans2 + 1即可

注意事项

1.数据很大,注意取模
2.ans应该为正数,可以使用

ans = (ans % mod + mod) % mod;

附上全部代码:

#include <cstdio>
#include <cctype>
typedef long long ll;
template <class T>
void r(T &x)
///Only for positive number
{
    x = 0;
    char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48) ,c = getchar();
}
const int mod = 1e9 + 7;
const int N = 1e6;
const int Pri = 78500;
ll cnt ,prime[Pri];
ll pn[Pri];
int n;
bool vis[N];
void sieve(int n)
{
    for (int i = 2;i <= n; i++)
    {
        if (!vis[i])
            prime[++cnt] = i;
        for (int j = 1;i * prime[j] <= n; j++)
        {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}
ll hyz(ll x ,ll y)
{
    ll ans = 1;
    do
    {
        if (y & 1)
            ans = ans * x % mod;
        x = x * x % mod;
    }while (y >>= 1);
    return ans;
}
int main()
{
    r(n);
    sieve(n);
    ll k = 1;
    for (int i = 1;i <= cnt; i++)
    {
        k = 1;
    ///在正数n!的素因子标准分解式中, 素数p的最高指数记作pn[n!]
    ///则pn[n!] = Σ(k >= 1) ceil(n / p ^ k)
        for (int j = 1;k * prime[i] <= n; j++)
            k *= prime[i] ,pn[i] += n / k;
        ///第j时即为prime[i] ^ j
    }
    k = 1;
    for (int i = 2;i <= n; i++)
        k *= i ,k %= mod;
    ll ans1 = 1 ,ans2 = 1;
    ///ans1表示n!以内的与n互质的数
    ///ans2表示n!的约数个数
    for (int i = 2;i <= n; i++)
         if (!vis[i])
             ans1 = ans1 * 1ll * (i - 1) % mod;
         else
             ans1 = ans1 * 1ll * i % mod;
    ///原本应是   if (!vis[i])
    ///              ans1 = ans1 * (i - 1) / i % mod;
    ///           else
    ///              ans1 = ans1 * i / i = ans1;
    ///而此题中, 应为求的是阶乘, 故自带一个 * i
    ///套取公式
    ///我们便可以把它消掉
    ///然后就ok了
    for (int i = 1;i <= cnt; i++)
        ans2 = ans2 * (pn[i] + 1) % mod;
        ///性质
        ///根据唯一分解定律, 任意数t = p1^a1 + p2^a2 + ... + pi^ai
        ///t的因数个数为ans = Π (ai + 1)
    k = k - ans1 - ans2 + 1;
    ///其中ans1表示n!以内的与n互质的数
    ///ans2表示n!的约数个数, 由于1被减了两次, 要加上一次
    ///其实就是:既然求不满足, 就把总的减去满足的再加上重复的就是解
    k = (k % mod + mod) % mod;
    ///个数为正数
    printf ("%lld" ,k);
    return 0;
}

Thanks for reading!

原创文章 23 获赞 41 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43537070/article/details/89180972