[HDU3439]Sequence
发布日期:2021-05-07 01:02:51 浏览次数:22 分类:原创文章

本文共 4888 字,大约阅读时间需要 16 分钟。

题目

思路

错排是众所周知的。这是一个计数,求一一对应关系 y = f ( x ) y=f(x) y=f(x) 的数量。该函数满足 ∀ x ∈ [ 1 , n ] ∩ Z , f ( x ) ≠ x \forall x\in[1,n]\cap\Z,f(x)\ne x x[1,n]Z,f(x)=x 。记录为 d ( n ) d(n) d(n)

我们用容斥去计算。枚举至少有多少个 x x x 满足 f ( x ) f(x) f(x) ,就可以写出

d ( n ) = ∑ i = 0 n ( − 1 ) i ( n i ) ( n − i ) ! = ∑ i = 0 n ( − 1 ) i ⋅ n ! i ! ⋅ ( n − i ) ! ⋅ ( n − i ) ! = ∑ i = 0 n ( − 1 ) i ⋅ n ! i ! = ∑ i = 0 n ( − 1 ) i ∏ j = i + 1 n j \begin{aligned} d(n)&=\sum_{i=0}^{n}(-1)^i{n\choose i}(n-i)!\\ &=\sum_{i=0}^{n}(-1)^i\cdot\frac{n!}{i!\cdot(n-i)!}\cdot(n-i)!\\ &=\sum_{i=0}^{n}\frac{(-1)^i\cdot n!}{i!}\\ &=\sum_{i=0}^{n}(-1)^i\prod_{j=i+1}^{n}j \end{aligned} d(n)=i=0n(1)i(in)(ni)!=i=0n(1)ii!(ni)!n!(ni)!=i=0ni!(1)in!=i=0n(1)ij=i+1nj

然后用倒数第二行的公式,可以看出递推式 d ( n ) = n ⋅ d ( n − 1 ) + ( − 1 ) n d(n)=n\cdot d(n-1)+(-1)^n d(n)=nd(n1)+(1)n

n n n 是用来补充 n ! n! n! 中相较于 d ( n − 1 ) d(n-1) d(n1) ( n − 1 ) ! (n-1)! (n1)! 的缺失, ( − 1 ) n (-1)^n (1)n 是加入的最后一项,就是 i = n i=n i=n 的时候。

但是,这个递推是 O ( n ) \mathcal O(n) O(n) 的,我没法优化它。想个办法,不如计算一下 d ( 2 p + 1 )   m o d   p d(2p+1)\bmod p d(2p+1)modp 的值。用公式的最后一行。注意到 ∏ j = i + 1 n j \prod_{j=i+1}^{n}j j=i+1nj 这一项在 i + 1 ≤ 2 p i+1\le 2p i+12p i ≤ 2 p − 1 i\le 2p-1 i2p1 的时候,都包含了 2 p 2p 2p 这一因数,故在取模意义下为零。我们只剩下两项, i ∈ { 2 p , 2 p + 1 } i\in\{2p,2p+1\} i{ 2p,2p+1} 的贡献,单独算。

d ( 2 p + 1 ) ≡ − 1 ⋅ ( 2 p + 1 ) + 1 × 1 ≡ 0 ( m o d p ) d(2p+1)\equiv -1\cdot(2p+1)+1\times 1\equiv 0\pmod{p} d(2p+1)1(2p+1)+1×10(modp)

毕竟 2 p + 1 ≡ 1 ( m o d p ) 2p+1\equiv 1\pmod{p} 2p+11(modp) 。然鹅,我们知道 d ( 1 ) = 0 = d ( 2 p + 1 ) d(1)=0=d(2p+1) d(1)=0=d(2p+1),这时候我们可以用递推公式计算 d ( 2 p + 2 ) d(2p+2) d(2p+2) 了。

d ( 2 p + 2 ) = ( 2 p + 2 ) ⋅ d ( 2 p + 1 ) + ( − 1 ) 2 p + 2 ≡ 2 ⋅ d ( 1 ) + ( − 1 ) 2 ( m o d p ) = d ( 2 ) \begin{aligned} d(2p+2)&=(2p+2)\cdot d(2p+1)+(-1)^{2p+2}\\ &\equiv 2\cdot d(1)+(-1)^2\pmod{p}\\ &=d(2) \end{aligned} d(2p+2)=(2p+2)d(2p+1)+(1)2p+22d(1)+(1)2(modp)=d(2)

注意到了吗?两个相乘的数字 ( 2 p + k + 1 ) (2p+k+1) (2p+k+1) d ( 2 p + k ) d(2p+k) d(2p+k) 在模 p p p 意义下同时出现了循环。

以此类推, d ( 2 p + 3 ) ≡ d ( 3 ) , d ( 2 p + 4 ) ≡ d ( 4 ) , … , d ( 2 p + k ) ≡ d ( k ) d(2p+3)\equiv d(3),d(2p+4)\equiv d(4),\dots,d(2p+k)\equiv d(k) d(2p+3)d(3),d(2p+4)d(4),,d(2p+k)d(k) 都是成立的。

最后的结论就是, d d d 出现了循环。我们有 d ( 2 p + k ) = d ( k ) ( k > 0 ) d(2p+k)=d(k)\quad(k>0) d(2p+k)=d(k)(k>0)

注意 d ( 2 p ) ≠ d ( 0 ) d(2p)\ne d(0) d(2p)=d(0) 。所以不要直接取模 2 p 2p 2p

现在我们只需要用 O ( p ) \mathcal O(p) O(p) 的时间计算了。然后这道题要求 ( n k ) d ( n − k )   m o d   m {n\choose k}d(n-k)\bmod m (kn)d(nk)modm ,就很简单了呗。组合数用 L u c a s Lucas Lucas 求解。

代码

#include <cstdio>#include <iostream>#include <vector>using namespace std;typedef long long int_;inline int readint(){       int x; scanf("%d",&x); return x;}inline int qkpow(int base,int q,int Mod){       int ans = 1; base %= Mod;    for(; q; q>>=1,base=1ll*base*base%Mod)        if(q&1) ans = 1ll*ans*base%Mod;    return ans;}int exgcd(int a,int b,int_ &x,int_ &y){       if(b == 0){           x = 1, y = 0; return a;    }    int d = exgcd(b,a%b,y,x);    y -= (a/b)*x; return d;}int getInv(int x,int p){       int_ res, useless;    if(exgcd(x%p,p,res,useless) != 1)        return -1; // impossible    return (res%p+p)%p;}int CRT(int n,int m[],int c[]){       int M = 1, ans = 0;    for(int i=1; i<=n; ++i) M *= m[i];    for(int i=1; i<=n; ++i){           int tmp = M/m[i];        tmp *= getInv(tmp,m[i]);        ans = (ans+1ll*tmp*c[i])%M;    }    return ans;}int function(int n,int p,int pk){       int res = 1; if(n < 2) return 1;    if(n >= pk){           for(int i=2; i<=pk; ++i)            if(i%p != 0)                res = 1ll*res*i%pk;        res = qkpow(res,n/pk,pk);    }    for(int i=2; i<=n%pk; ++i)        if(i%p != 0)            res = 1ll*res*i%pk;    return 1ll*res*function(n/p,p,pk)%pk;}int calc(int n,int p){       if(n < p) return 0; return n/p+calc(n/p,p);}int exLucas(int n,int m,int p,int pk){       int pt = calc(n,p)-calc(m,p)-calc(n-m,p);    int jb = function(n,p,pk); // zxy orz    jb = 1ll*jb*getInv(function(m,p,pk),pk)%pk;    jb = 1ll*jb*getInv(function(n-m,p,pk),pk)%pk;    return 1ll*jb*qkpow(p,pt,pk)%pk;}int mods[50], yu[50];int Lucas(int n,int m,int p){       int cnt = 0;    for(int i=2,j; 1ll*i*i<=p; ++i){           if(p%i != 0) continue;        for(j=1; p%i==0; j*=i) p /= i;        mods[++ cnt] = j;        yu[cnt] = exLucas(n,m,i,j);    }    if(p != 1){           mods[++ cnt] = p;        yu[cnt] = exLucas(n,m,p,p);    }    return CRT(cnt,mods,yu);}signed main(){       for(int T=readint(),kase=0; T; --T){           int n = readint(), m = readint();        int p = readint(), cp = 1;        if(n < m or p == 1){               printf("Case %d: 0\n",++kase);            continue;        }        int t = (n-m-1)%(p<<1)+1;        for(int i=1; i<=t; ++i)            cp = (1ll*cp*i+1-(i<<1&2))%p;        if(cp < 0) cp += p;        printf("Case %d: %lld\n",++kase,1ll*Lucas(n,m,p)*cp%p);    }    return 0;}
上一篇:es6常用语法
下一篇:vue-cli创建项目实例

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年04月14日 08时15分42秒