Loading

P6031 CF1278F Cards 加强版

P6031 CF1278F Cards 加强版

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

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

深呼吸。。。开始!


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

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

答案是

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

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

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

\[m^n=\sum_{i=0}^{m}\begin{Bmatrix}n\\i\end{Bmatrix}i!\binom{m}{i} \]

直接往答案里带

\[\sum_{i=0}^{n}\binom{n}{i}p^i(1-p)^{n-i}\sum_{j=0}^{i}\begin{Bmatrix}k\\j\end{Bmatrix}j!\binom{i}{j}\\ =\sum_{j=0}^{k}\begin{Bmatrix}k\\j\end{Bmatrix}j!\sum_{i=0}^{n}\binom{n}{i}p^i(1-p)^{n-i}\binom{i}{j}\\ \]

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

\[\sum_{i=0}^{n}\binom{n}{i}p^i(1-p)^{n-i}\binom{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}\binom{n-j}{i-j}\binom{n}{j}p^i(1-p)^{n-i}\\ \]

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

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

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

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

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

带回去

\[\text{原式}=\sum_{j=0}^{k}\begin{Bmatrix}k\\j\end{Bmatrix}j!p^j\binom{n}{j} \]

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

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

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

\[\begin{Bmatrix}n\\m\end{Bmatrix}=\dfrac{1}{m!}\sum_{i=0}^{m}(-1)^{m-i}\binom{m}{i}i^m \]

然后暴力带入拆式子

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

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

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

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

\[=\sum_{i=0}^{k}i^k\binom{n}{i}p^i\sum_{j=0}^{k-i}(-p)^{j}\binom{n-i}{j} \]

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

\[f(i)=\sum_{j=0}^{k-i}(-p)^{j}\binom{n-i}{j}\\ =\sum_{j=0}^{k-i}(-p)^{j}(\binom{n-i-1}{j}+\binom{n-i-1}{j-1})\\ =\sum_{j=0}^{k-i-1}(-p)^{j}\binom{n-i-1}{j}+(-p)^{k-i}\binom{n-i-1}{k-i}+\sum_{j=1}^{k-i}(-p)^{j}\binom{n-i-1}{j-1}\\ =f(i+1)+(-p)^{k-i}\binom{n-i-1}{k-i}+(-p)\sum_{j=0}^{k-i-1}(-p)^{j}\binom{n-i-1}{j}\\ =(1-p)f(i+1)+(-p)^{k-i}\binom{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\) ,是固定的。

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

\[\binom{n}{m}=\dfrac{n}{m}\binom{n-1}{m-1} \]

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

边界为 \(f(k)=1\)

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

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

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

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

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

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


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

忽然发现组合数直接用

\[\binom{n}{i}\dfrac{n-i}{i+1}=\binom{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\n",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\n",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

【upd】 是 disangan333 而不是 disangan233 。。。

线性筛的时候开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\n",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\n",ans),exit(0);
}

}


signed main(){
	n=read(),p=qpow(read(),mod-2),k=read();
	if(n<=k)solve1::main();
	else solve2::main();
}
posted @ 2021-01-08 21:01  zzctommy  阅读(129)  评论(0编辑  收藏  举报