题解 luoguP2155 【[SDOI2008]沙拉公主的困惑】

这题真喵喵的毒瘤,写的时候被时空双卡。

为了发泄愤怒,我来水一篇题解

题意:求 1 − n ! 1-n! 1n! 中与 m ! m! m! 互质的数的个数 ( m &lt; = n ) (m&lt;=n) (m<=n)

我们发现小于 m ! m! m! 的数中,与 m ! m! m! 互质的数有 φ ( m ! ) φ(m!) φ(m!)个,那如何求大于 m ! m! m! 的数中与 m ! m! m! 互质的数的个数呢?

引理:若 a , b a,b a,b互质,则 a + b × k a+b\times k a+b×k b b b互质。

证明:当 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1时, g c d ( b , a % b ) = 1 gcd(b,a\% b)=1 gcd(b,a%b)=1

∵ a % b = a − b × k \because a\% b=a-b\times k a%b=ab×k

∴ g c d ( b , a − b × k ) = 1 \therefore gcd(b,a-b\times k)=1 gcd(b,ab×k)=1

得证。

那么对于一个小于 m ! m! m! 且与 m ! m! m! 互质的数 x x x x + 1 × m ! x+1\times m! x+1×m! x + 2 × m ! x+2\times m! x+2×m! …都与 m ! m! m! 互质。

因为 n &gt; = m n&gt;=m n>=m,所以 n ! n! n! m ! m! m!的倍数,那么 1 − n ! 1-n! 1n! 中与 m ! m! m! 互质的数的个数,根据上面的结论,很显然就是:

r e s = φ ( m ! ) × n ! m ! = m ! × ∏ i = 1 k ( p i − 1 p i ) × n ! m ! = n ! × ∏ i = 1 k ( p i − 1 p i ) res=φ(m!)\times \frac{n!}{m!}=m!\times \prod\limits_{i=1}^{k}\left (\dfrac{p_i-1}{p_i}\right)\times \frac{n!}{m!}=n!\times \prod\limits_{i=1}^{k}\left (\dfrac{p_i-1}{p_i}\right) res=φ(m!)×m!n!=m!×i=1k(pipi1)×m!n!=n!×i=1k(pipi1)

我们用 a n s ans ans数组记录最后的式子的右边的东西, a n s [ i ] ans[i] ans[i]表示 k k k i i i时右边东西的值。 j c [ i ] jc[i] jc[i]表示 i ! i! i! n y [ i ] ny[i] ny[i]表示 i i i的逆元。

根据公式,很容易就能写出,答案 = j c [ n ] × a n s [ m ] =jc[n]\times ans[m] =jc[n]×ans[m]。预处理出三个数组,就能做到 O ( 1 ) O(1) O(1)查询,这题就做完了,撒花~。

空间有点紧,不能都开 l o n g   l o n g long \ long long long,不该开的就不用开了。

#include<bits/stdc++.h>
#define ts cout<<"ok"<<endl
#define oo (1e18)
#define ll long long
#define LL unsigned long long
#define hh puts("")
using namespace std;
int T,top,n,m,ny[10000005],ans[10000005],pr[6000005];
bool v[10000005];
ll r,jc[10000005];
inline int read(){
    int ret=0,ff=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)){ret=(ret<<3)+(ret<<1)+(ch^48);ch=getchar();}
    return ret*ff;
}
signed main(){
    scanf("%d%lld",&T,&r);
    jc[0]=jc[1]=1;
    ny[0]=ny[1]=1;
    ans[0]=ans[1]=1;
    for(int i=2;i<=10000000;i++){
        jc[i]=jc[i-1]*(ll)i%r;
        ny[i]=(ll)(r-r/i)*ny[r%i]%r;
        if(!v[i]) pr[++top]=i;
        for(int j=1;j<=top&&i*pr[j]<=10000000;j++){
            v[i*pr[j]]=1;
            if(i%pr[j]==0) break;
        }
    }
    for(int i=2;i<=10000000;i++){
        ans[i]=ans[i-1];
        if(!v[i]) ans[i]=(ll)ans[i]%r*(i-1)%r*ny[i]%r;
    }
    while(T--){
        n=read(),m=read();
        ll res=(ll)ans[m]%r*jc[n]%r;
        printf("%lld\n",res);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: NOIP2008 普及组复赛试题是中国国家信息学奥林匹克选手选拔赛的试题之一,比较经典且有一定难度。以下是对该试题的解答。 本次试题主要有三道题目,分别是小数排列、整数取位和检测型括号序列。 小数排列这道题要求给定一个正整数 n,求小于 n 的所有符合要求的小数的个数。解决这一题的方法是利用排列组合的知识,找出符合要求的小数的模式并计算其个数。具体的代码实现就需要对 n 进行拆分,计算个位数、十位数和百位数的可能情况并相乘即可得到结果。 整数取位这道题要求给定一个整数 n 和一个非负整数 m,求 n 的第 m 个数字。解决这一题的方法是将整数 n 转化为字符串,然后通过字符串的索引来获取第 m 个数字。 检测型括号序列这道题要求判断给定的一个仅包含左右括号的字符串是否是合法的括号序列。解决这一题的方法是使用栈的数据结构,遍历字符串,对于每个遇到的左括号,将其压入栈中;对于每个遇到的右括号,检查栈顶元素是否为对应的左括号,若是,则弹出栈顶元素,否则返回不合法。 以上就是对 NOIP2008 普及组复赛试题的简要解答,其中涉及到的算法和数据结构是编程中比较常见的基础知识,通过理解和掌握这些知识,可以帮助我们更好地解决类似的编程问题。 ### 回答2: NOIP(全国青少年信息学奥林匹克竞赛)是中国举办的一项信息学竞赛活动。2008年,NOIP举办了普及组复赛。以下是对该年度复赛试题进行的解答。 复赛试题一共有三大题目,分别涉及到图的遍历、数学运算和字符串处理。 第一题是关于图的遍历的。题目给出一张有向图和一个起始节点,要求按照拓扑排序的原则遍历整个图,并输出遍历的结果。拓扑排序是一种将有向无环图的顶点进行排序的算法,具体实现可以使用DFS或者BFS。根据题目给出的起始节点,我们可以使用DFS从该节点开始遍历图,并使用一个栈来存储遍历的结果。 第二题是一个数学运算的题目,要求计算一个给定数的乘方结果的各位数字之和。这题可以通过将给定数转化为字符串,然后对字符串中的每位数字进行相加来解答。也可以将给定数分解为各位数字相加的形式。具体实现上可以使用循环或者递归的方式。 第三题是一个关于字符串处理的任务,要求将输入字符串中的数字字符提取出来,并计算所有数字的平均值。这个问题可以通过遍历字符串的方式来解决。对于每个字符,我们判断是否为数字字符,是的话就将其转换为数字并累加到一个总和上。最后将总和除以数字字符的个数,得到平均值。 总体来说,NOIP 2008 普及组复赛试题涵盖了图的遍历、数学运算和字符串处理的内容。通过解答这些问题,可以增强对这些概念的理解,并提升解决实际问题的能力。 ### 回答3: NOIP2008普及组复赛试题是一道考察动态规划和递归思想的题目。题目给出了一个整数n,要求计算出整数1到n的所有排列中,满足以下条件的排列的个数: 1.相邻两个数的差的绝对值不能等于1; 2.排列中的数不能重复。 首先,我们需要定义一个函数f(n),表示整数1到n的满足条件的排列的个数。我们可以将这个问题转化为子问题,即如何计算f(n-1)和f(n-2)等。 根据题目要求,我们可以发现f(n)的值由两部分组成:一部分是以n结尾的满足条件的排列的个数,另一部分是不以n结尾的满足条件的排列的个数。 对于第一部分,即以n结尾的排列,我们可以将其分为两种情况:n和n-2相邻,以及n和n-2不相邻。如果n和n-2相邻,那么有f(n-2)种情况。如果n和n-2不相邻,那么可以在以n-1结尾的排列后面加上n,所以有f(n-1)种情况。因此,以n结尾的满足条件的排列的个数为f(n) = f(n-1) + f(n-2)。 对于第二部分,即不以n结尾的排列,其个数就是f(n-1)。 所以,f(n) = f(n-1) + f(n-2) + f(n-1) = 2*f(n-1) + f(n-2)。 基本情况是当n=1时,满足条件的排列只有1个,即f(1)=1;当n=2时,满足条件的排列有2个,即f(2)=2。 通过递推,可以得到整个解空间中满足条件的排列的个数。 在编程实现时,可以使用动态规划来解决这道题。先定义一个大小为n+1的数组dp,dp[i]表示整数1到i的满足条件的排列的个数。然后,通过循环从3到n,依次计算dp[i]的值,最后返回dp[n]即可。 总结起来,这道题是通过递推和动态规划的思想来计算满足条件的排列的个数。通过定义状态转移方程,将大问题转化为小问题,最后通过循环计算得出结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值