P6031 CF1278F Cards 加强版
如果你看过我 这篇文章 ,前言说的题解看了一上午只看懂一半就是这题。
做完了幼儿园篮球题,感觉说不定可以再试试,而且式子看完就忘了,自己再推一遍最好。
深呼吸。。。开始!
显然出题人要我们 \(O(k)\) 求答案。
第一张牌是王牌的概率是 \(\dfrac{(m-1)!}{m!}=\dfrac{1}{m}\) 设为 \(p\)
答案是
就是枚举有几轮第一张是王牌。
下式不会请看开头的链接。
直接往答案里带
后面出现了一个奇奇怪怪的东西,单独提出来算。
观察到 \(i\ge j\) 时组合数才不为 \(0\) ,因此提出 \(p^j\)
发现后面是一个二项式定理一样的东西,直接化成二项式
注意这东西在 \(n<k\) 时并不成立,要特判。
带回去
现在已经可以 \(O(k\log k)\) 了,求出一行第二类斯特林数即可。
然而离 \(O(k)\) 差的还挺远,我们不能使用任何带NTT的东西。
下式不会请看开头的链接。
然后暴力带入拆式子
有点卡住了,观察了一下,发现 \(i\) 丢到前面去会方便很多。
还是提出一个 \((-p)^{i}\) 出来。不过这个作用好像只是消掉 \((-1)^{i}\) ,不能产生像上次那样优美的结论。
令 \(f(i)=\sum\limits_{j=0}^{k-i}(-p)^{j}\dbinom{n-i}{j}\) ,能求出 \(f\) 就解决了。
一开始把 \(k-i\) 抄成 \(k-1\) 了,做半天做不下去/kk
我们惊奇的发现只需要求出 \((-p)^{k-i}\dbinom{n-i-1}{k-i}\) 就可以直接递推。
同时又发现 \(\dbinom{n-i-1}{k-i}\) 上下指标的差是 \(n-k-1\) ,是固定的。
如果你看过《具体数学》就会知道二项式系数有个叫做吸收率的东西。阶乘展开即证。
直接拿这玩意递推就好了。
边界为 \(f(k)=1\) 。
注意一下这里上指标是 \(n-i-1\) ,所以 \(n>k\) 才可以这么做,之前的特判应该改为 \(n\le k\)
注意 \(i^k\) 要线性筛。完全积性函数随便筛了。
终终终终终于推完了!!!现在回去看幼儿园篮球题,是不是真的感觉像幼儿园题了qwq
或许这题可以叫做,斯特林数基础练习题?不过这题的式子长度是莫比乌斯反演基础练习题 第三部分 的 \(\dfrac{2}{3}\) (不过前两个部分有手就行)
写到这里回去看自己之前写的幽灵乐团的题解,发现不会了/kk
现在建议你自己去实现一下,实现起来还是有点好玩的qwq。下面是讲怎么实现的。
有了幼儿园篮球题的教训,空间别乱开,256MB还是有点紧的。代码实现就这个有点困难了吧!
忽然发现组合数直接用
边递推边计算会松很多,可以少开两个数组。(阶乘及其逆元)
同理,\(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();
}