zoukankan      html  css  js  c++  java
  • P6031 CF1278F Cards 加强版

    P6031 CF1278F Cards 加强版

    如果你看过我 这篇文章 ,前言说的题解看了一上午只看懂一半就是这题。

    做完了幼儿园篮球题,感觉说不定可以再试试,而且式子看完就忘了,自己再推一遍最好。

    深呼吸。。。开始!


    显然出题人要我们 (O(k)) 求答案。

    第一张牌是王牌的概率是 (dfrac{(m-1)!}{m!}=dfrac{1}{m}) 设为 (p)

    答案是

    [sum_{i=0}^{n}inom{n}{i}p^i(1-p)^{n-i}i^k ]

    就是枚举有几轮第一张是王牌。

    下式不会请看开头的链接。

    [m^n=sum_{i=0}^{m}egin{Bmatrix}n\iend{Bmatrix}i!inom{m}{i} ]

    直接往答案里带

    [sum_{i=0}^{n}inom{n}{i}p^i(1-p)^{n-i}sum_{j=0}^{i}egin{Bmatrix}k\jend{Bmatrix}j!inom{i}{j}\ =sum_{j=0}^{k}egin{Bmatrix}k\jend{Bmatrix}j!sum_{i=0}^{n}inom{n}{i}p^i(1-p)^{n-i}inom{i}{j}\ ]

    后面出现了一个奇奇怪怪的东西,单独提出来算。

    [sum_{i=0}^{n}inom{n}{i}p^i(1-p)^{n-i}inom{i}{j}\ =sum_{i=0}^{n}dfrac{n!}{i!(n-i)!}dfrac{i!}{j!(i-j)!}p^i(1-p)^{n-i}\ =sum_{i=0}^{n}dfrac{(n-j)!}{(n-i)!(i-j)!}dfrac{n!}{(n-j)!j!}p^i(1-p)^{n-i}\ =sum_{i=0}^{n}inom{n-j}{i-j}inom{n}{j}p^i(1-p)^{n-i}\ ]

    观察到 (ige j) 时组合数才不为 (0) ,因此提出 (p^j)

    [=p^jinom{n}{j}sum_{i=0}^{n-j}inom{n-j}{i}p^i(1-p)^{n-j-i}\ ]

    发现后面是一个二项式定理一样的东西,直接化成二项式

    [=p^jinom{n}{j}(p+1-p)^{n-j}\ =p^jinom{n}{j} ]

    注意这东西在 (n<k) 时并不成立,要特判。

    带回去

    [ ext{原式}=sum_{j=0}^{k}egin{Bmatrix}k\jend{Bmatrix}j!p^jinom{n}{j} ]

    现在已经可以 (O(klog k)) 了,求出一行第二类斯特林数即可。

    然而离 (O(k)) 差的还挺远,我们不能使用任何带NTT的东西。

    下式不会请看开头的链接。

    [egin{Bmatrix}n\mend{Bmatrix}=dfrac{1}{m!}sum_{i=0}^{m}(-1)^{m-i}inom{m}{i}i^m ]

    然后暴力带入拆式子

    [sum_{j=0}^{k}dfrac{1}{j!}sum_{i=0}^{j}(-1)^{j-i}inom{j}{i}i^kj!p^jinom{n}{j}\ =sum_{j=0}^{k}p^jinom{n}{j}(-1)^{j}sum_{i=0}^{j}(-1)^{i}inom{j}{i}i^k\ =sum_{j=0}^{k}(-p)^jsum_{i=0}^{j}inom{n}{j}inom{j}{i}(-1)^{i}i^k\ =sum_{j=0}^{k}(-p)^jsum_{i=0}^{j}dfrac{n!}{j!(n-j)!}dfrac{j!}{i!(j-i)!}(-1)^{i}i^k\ =sum_{j=0}^{k}(-p)^jsum_{i=0}^{j}dfrac{n!}{(n-j)!}dfrac{1}{i!(j-i)!}(-1)^{i}i^k\ =sum_{j=0}^{k}(-p)^jsum_{i=0}^{j}dfrac{(n-i)!}{(n-j)!(j-i)!}dfrac{n!}{i!(n-i)!}(-1)^{i}i^k\ =sum_{j=0}^{k}(-p)^jsum_{i=0}^{j}inom{n-i}{j-i}inom{n}{i}(-1)^{i}i^k\ ]

    有点卡住了,观察了一下,发现 (i) 丢到前面去会方便很多。

    [=sum_{i=0}^{k}(-1)^{i}i^kinom{n}{i}sum_{j=i}^{k}(-p)^{j}inom{n-i}{j-i} ]

    还是提出一个 ((-p)^{i}) 出来。不过这个作用好像只是消掉 ((-1)^{i}) ,不能产生像上次那样优美的结论。

    [=sum_{i=0}^{k}i^kinom{n}{i}p^isum_{j=0}^{k-i}(-p)^{j}inom{n-i}{j} ]

    (f(i)=sumlimits_{j=0}^{k-i}(-p)^{j}dbinom{n-i}{j}) ,能求出 (f) 就解决了。

    [f(i)=sum_{j=0}^{k-i}(-p)^{j}inom{n-i}{j}\ =sum_{j=0}^{k-i}(-p)^{j}(inom{n-i-1}{j}+inom{n-i-1}{j-1})\ =sum_{j=0}^{k-i-1}(-p)^{j}inom{n-i-1}{j}+(-p)^{k-i}inom{n-i-1}{k-i}+sum_{j=1}^{k-i}(-p)^{j}inom{n-i-1}{j-1}\ =f(i+1)+(-p)^{k-i}inom{n-i-1}{k-i}+(-p)sum_{j=0}^{k-i-1}(-p)^{j}inom{n-i-1}{j}\ =(1-p)f(i+1)+(-p)^{k-i}inom{n-i-1}{k-i} ]

    一开始把 (k-i) 抄成 (k-1) 了,做半天做不下去/kk

    我们惊奇的发现只需要求出 ((-p)^{k-i}dbinom{n-i-1}{k-i}) 就可以直接递推。

    同时又发现 (dbinom{n-i-1}{k-i}) 上下指标的差是 (n-k-1) ,是固定的。

    如果你看过《具体数学》就会知道二项式系数有个叫做吸收率的东西。阶乘展开即证。

    [inom{n}{m}=dfrac{n}{m}inom{n-1}{m-1} ]

    直接拿这玩意递推就好了。

    边界为 (f(k)=1)

    注意一下这里上指标是 (n-i-1) ,所以 (n>k) 才可以这么做,之前的特判应该改为 (nle k)

    注意 (i^k) 要线性筛。完全积性函数随便筛了。

    终终终终终于推完了!!!现在回去看幼儿园篮球题,是不是真的感觉像幼儿园题了qwq

    或许这题可以叫做,斯特林数基础练习题?不过这题的式子长度是莫比乌斯反演基础练习题 第三部分(dfrac{2}{3}) (不过前两个部分有手就行)

    写到这里回去看自己之前写的幽灵乐团的题解,发现不会了/kk

    现在建议你自己去实现一下,实现起来还是有点好玩的qwq。下面是讲怎么实现的。


    有了幼儿园篮球题的教训,空间别乱开,256MB还是有点紧的。代码实现就这个有点困难了吧!

    忽然发现组合数直接用

    [inom{n}{i}dfrac{n-i}{i+1}=inom{n}{i+1} ]

    边递推边计算会松很多,可以少开两个数组。(阶乘及其逆元)

    同理,(p^k) 边递推边计算可以少开一个数组。

    线性筛的时候拿逆元的数组当作vis数组可以少开一个bool,质数数组暂时拿另一个数组代替,可以少开1e6,不过没啥用。

    用上面的方法压一波空间可以搞成115MB,只不过我自己都觉得代码不太可读。。。

    草怎么直接跑成最优解了/fad

    #define mod 998244353
    const int N=10000005;
    int n,k,p,ans;
    int f[N];
    
    namespace math{
    int id[N],pct,inv[N];
    inline int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
    inline void fmod(int&x){x-=mod,x+=x>>31&mod;}
    void initmath(const int&n=N-1){
    	id[1]=1,id[0]=0;
    	for(int i=2;i<=n;++i){
    		if(!inv[i])f[++pct]=i,id[i]=qpow(i,k);
    		for(int j=1;i*f[j]<=n&&j<=pct;++j){
    			inv[i*f[j]]=1,id[i*f[j]]=1ll*id[i]*id[f[j]]%mod;
    			if(i%f[j]==0)break;
    		}
    	}
    	inv[1]=1;for(int i=2;i<=n;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
    }
    
    }
    using namespace math;
    
    namespace solve1{
    void main(){
    	f[0]=1;for(int i=1;i<=n;++i)f[i]=1ll*(mod+1-p)*f[i-1]%mod;
    	for(int i=0,j=1,y=1;i<=n;++i){
    		fmod(ans+=1ll*j*y%mod*f[n-i]%mod*id[i]%mod);
    		j=1ll*j*(n-i)%mod*inv[i+1]%mod,y=1ll*y*p%mod;
    	}
    	printf("%d
    ",ans),exit(0);
    }
    
    }
    
    namespace solve2{
    void main(){
    	f[k]=1;
    	for(int i=k-1,x=0,z=1,y=1;i>=0;--i){
    		++x,z=1ll*z*(x+n-k-1)%mod*inv[x]%mod,y=1ll*y*(mod-p)%mod;
    		fmod(f[i]=1ll*(mod+1-p)*f[i+1]%mod),fmod(f[i]+=1ll*y*z%mod);
    	}
    	for(int i=0,j=1,y=1;i<=k;++i){
    		fmod(ans+=1ll*id[i]*j%mod*y%mod*f[i]%mod);
    		j=1ll*j*(n-i)%mod*inv[i+1]%mod,y=1ll*y*p%mod;
    	}
    	printf("%d
    ",ans),exit(0);
    }
    
    }
    
    signed main(){
    	n=read(),p=qpow(read(),mod-2),k=read(),initmath(min(n,k)+3);
    	if(n<=k)solve1::main();
    	else solve2::main();
    }
    

    然后来点劲爆的东西,这个空间可以压到80MB的!!!(稍微拿常数换了点空间罢了)

    但是我还是不知道为啥disangan233可以76MB

    线性筛的时候开1e6的质数数组,4MB(大概和dsg就差这里了吧?)。

    vis数组开成bitset压掉64倍空间。

    然后开两个1e7的数组 (f,g) ,76MB

    solve1的时候,先把 ((1-p)^i) 存到 (f) ,然后线性筛 (id_k) 存到 (g) ,然后把 (f) 的值更新 (f_i=f_ig_{n-i}) ,接着把 (g) 换成逆元数组,再一样跑循环。

    solve2的时候,先把逆元存到 (g) ,然后就可以跑出 (f) ,接着把 (id_k) 存到 (g) ,把 (f) 的值更新为 (f_i=f_ig_i) ,然后再把 (g) 换成逆元数组(这里多扫了一遍啊,于是总时间慢了0.6s),就可以跑下面的循环了。

    卡空间真好玩qwq

    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define mkp(x,y) make_pair(x,y)
    #define pb(x) push_back(x)
    #define sz(v) (int)v.size()
    typedef long long LL;
    typedef double db;
    template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
    template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
    #define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
    #define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    	return f?x:-x;
    }
    #define mod 998244353
    const int N=10000005;
    int n,k,p,ans;
    int f[N],g[N];
    
    namespace math{
    int pct,pri[N/10];
    bitset<N>vis;
    inline int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
    inline void fmod(int&x){x-=mod,x+=x>>31&mod;}
    void Sieve(const int&n=N-1){
    	g[1]=1,g[0]=0,vis.reset();
    	for(int i=2;i<=n;++i){
    		if(!vis[i])pri[++pct]=i,g[i]=qpow(i,k);
    		for(int j=1;i*pri[j]<=n&&j<=pct;++j){
    			vis[i*pri[j]]=1,g[i*pri[j]]=1ll*g[i]*g[pri[j]]%mod;
    			if(i%pri[j]==0)break;
    		}
    	}
    }
    
    }
    using namespace math;
    
    namespace solve1{
    void main(){
    	Sieve(n);
    	f[0]=1;for(int i=1;i<=n;++i)f[i]=1ll*(mod+1-p)*f[i-1]%mod;
    	for(int i=0;i<=n;++i)f[i]=1ll*f[i]*g[n-i]%mod;
    	g[1]=1;for(int i=2;i<=n;++i)g[i]=1ll*g[mod%i]*(mod-mod/i)%mod;
    	for(int i=0,j=1,y=1;i<=n;++i){
    		fmod(ans+=1ll*j*y%mod*f[n-i]%mod);
    		j=1ll*j*(n-i)%mod*g[i+1]%mod,y=1ll*y*p%mod;
    	}
    	printf("%d
    ",ans),exit(0);
    }
    
    }
    
    namespace solve2{
    void main(){
    	f[k]=1;
    	g[1]=1;for(int i=2;i<=k;++i)g[i]=1ll*(mod-mod/i)*g[mod%i]%mod;
    	for(int i=k-1,x=0,z=1,y=1;i>=0;--i){
    		++x,z=1ll*z*(x+n-k-1)%mod*g[x]%mod,y=1ll*y*(mod-p)%mod;
    		fmod(f[i]=1ll*(mod+1-p)*f[i+1]%mod),fmod(f[i]+=1ll*y*z%mod);
    	}
    	Sieve(k);
    	for(int i=0;i<=k;++i)f[i]=1ll*f[i]*g[i]%mod;
    	g[1]=1;for(int i=2;i<=k+1;++i)g[i]=1ll*(mod-mod/i)*g[mod%i]%mod;
    	for(int i=0,j=1,y=1;i<=k;++i){
    		fmod(ans+=1ll*f[i]*j%mod*y%mod);
    		j=1ll*j*(n-i)%mod*g[i+1]%mod,y=1ll*y*p%mod;
    	}
    	printf("%d
    ",ans),exit(0);
    }
    
    }
    
    
    signed main(){
    	n=read(),p=qpow(read(),mod-2),k=read();
    	if(n<=k)solve1::main();
    	else solve2::main();
    }
    
  • 相关阅读:
    vi/vim经常使用命令
    微信公众平台开发(数据库连接)
    遍历Map的四种方法
    提高日志质量的 5 大技巧
    位运算 的探究
    STL源代码剖析 读书总结
    从一段代码看fork()函数及其引发的竞争
    oc56--ARC多个对象的内存管理
    oc55--ARC单个对象的内存管理
    oc54--auatorelease应用场景
  • 原文地址:https://www.cnblogs.com/zzctommy/p/14253380.html
Copyright © 2011-2022 走看看