LG P2155 【SDOI2008】 沙拉公主的困惑 解题报告

LG P2155 【SDOI2008】 沙拉公主的困惑 解题报告

题目链接

根据题意,所求即为

a n s = ∑ i = 1 N ! [ gcd ⁡ ( i , M ! ) = 1 ] ans=\sum_{i=1}^{N!}[\gcd(i,M!)=1] ans=i=1N![gcd(i,M!)=1]

由于 M ≤ N M\le N MN,容易知道 M ! ∣ N ! M!\mid N! M!N!

结合 g c d gcd gcd 的性质,若 gcd ⁡ ( i , M ! ) = c \gcd(i,M!)=c gcd(i,M!)=c,则 gcd ⁡ ( i + k M ! , M ! ) = c \gcd(i+kM!,M!)=c gcd(i+kM!,M!)=c。所以把 M ! M! M! 看成一种循环节,有

a n s = N ! M ! ∑ i = 1 M ! [ i ⊥ M ! ] = N ! M ! φ ( M ! ) ans=\dfrac{N!}{M!}\sum_{i=1}^{M!}[i\perp M!]=\dfrac{N!}{M!}\varphi(M!) ans=M!N!i=1M![iM!]=M!N!φ(M!)

用欧拉函数的定义展开,有

a n s = N ! M ! ⋅ M ! ∏ p ∣ M ! p − 1 p = N ! ∏ p ≤ M ( p − 1 ) ∏ p ≤ M p ans=\dfrac{N!}{M!}\cdot M!\prod_{p|M!}\dfrac{p-1}{p}=N!\dfrac{\prod_{p\le M}(p-1)}{\prod_{p\le M}p} ans=M!N!M!pM!pp1=N!pMppM(p1)

这样,预处理三部分,即可 O ( N ) O(N) O(N) 预处理, O ( 1 ) O(1) O(1) 计算。注意模数的分类讨论。

/*
 * @Author: rijuyuezhu
 * @Date: 2022-09-02 17:00:03
 * @Description: https://www.luogu.com.cn/problem/P2155
 * @Tag: 数论,欧拉函数
*/
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), tt == ss) ? EOF : *ss++)
ll read() {
   ll x = 0, f = 1; char ch = getchar();
   for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
   for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
   return x * f;
}
const int MAXN = 1e7 + 5;
int P;
int pr[MAXN], tot, fac[MAXN], pd_pd1[MAXN], ivpd_p[MAXN];
bool np[MAXN];
int qpow(int a, int n) {
   int ret = 1;
   for(; n; n >>= 1, a = 1ll * a * a % P)
      if(n & 1) ret = 1ll * ret * a % P;
   return ret;
}
void init(int n) {
   np[1] = 1;
   for(int i = 2; i <= n; i++) {
      if(!np[i])
         pr[++tot] = i;
      for(int j = 1; j <= tot && pr[j] <= n / i; j++) {
         np[i * pr[j]] = 1;
         if(i % pr[j] == 0) break;
      }
   }
   fac[0] = 1;
   for(int i = 1; i <= n; i++)
      if(i == P) fac[i] = fac[i-1];
      else fac[i] = 1ll * fac[i-1] * i % P;
   pd_pd1[0] = ivpd_p[0] = 1;
   for(int i = 1; i <= tot; i++) {
      pd_pd1[i] = 1ll * pd_pd1[i-1] * (pr[i] - 1) % P;
      if(pr[i] == P)
         ivpd_p[i] = ivpd_p[i-1];
      else 
         ivpd_p[i] = 1ll * ivpd_p[i-1] * qpow(pr[i], P-2) % P;
   }
}
int main() {
   int t = read();
   P = read();
   init(1e7);
   for(int i = 1; i <= t; i++) {
      int n = read(), m = read();
      int id = upper_bound(pr + 1, pr + 1 + tot, m) - pr - 1; // id of the first num that <= m
      if(P <= m)
         printf("%lld\n", 1ll * fac[n] * pd_pd1[id] % P * ivpd_p[id] % P); 
      else if(P < n)
         printf("0\n");
      else if(P == n) {
         if(m == n) printf("%lld\n", 1ll * fac[n] * pd_pd1[id] % P * ivpd_p[id] % P);
         else printf("0\n");
      } else //P > n
         printf("%lld\n", 1ll * fac[n] * pd_pd1[id] % P * ivpd_p[id] % P);
   }
   return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值