PE427 n-sequences 和 ZJOI2020 抽卡
n-sequences
对于⼀个序列\(S\),令\(L(S)\)表示\(S\)中最长的值相同的子串的⻓度。
令\(f(n)\)表示对于所有\(n^n\)个长度为\(n\)的每个数值都在\(1\)到\(n\)之间序列的\(L\)值总和。
求\(f(7.5e6)\)。
题解
⾸先转化为求\(L(S)\geq 1, L(S)\geq 2, …\)的方案然后相加。
接着补集转化为\(L(S)\leq k\)的方案,也就是每段都不超过\(k\)的方案。
设\(g(i)\)表示满足条件的长度为\(i\)的序列的种数,有\(g(0)=1\)。
-
当\(1\leq i\leq k\)时,\(g(i)=n\times g(i-1)\)。
-
当\(i=k+1\)时,恰好有\(n\)种方案不合法,\(g(i)=n\times g(i-1)-n\times g(0)\)。
-
当\(k+2\leq i\leq n\)时,容斥掉\(i-k\sim i\)都是同色的的方案。那么由于\(g(i-1)\)的限制,\(i-k-1\)和\(i-k\)的颜色必须不同。所以\(g(i)=n\times g(i-1)-(n-1)\times g(i-k-1)\)。
直接计算的话时间复杂度\(O(n^2)\)不可取。
CO int N=100;
int64 g[N];
int main(){
int n=11;
int64 pwr=pow(n,n),ans=0;
for(int k=0;k<n;++k){
g[0]=1;
for(int i=1;i<=k;++i) g[i]=n*g[i-1];
g[k+1]=n*g[k]-n*g[0];
for(int i=k+2;i<=n;++i) g[i]=n*g[i-1]-(n-1)*g[i-k-1];
ans+=pwr-g[n];
}
printf("%lld\n",ans);
return 0;
}
假设没有第2种转移,考虑这个递推式的组合意义。相当于走楼梯,往上走\(1\)步的代价是乘\(n\),走\(k+1\)步的代价是乘\(-(n-1)\)。
那么我们枚举一共做了多少次走\(k+1\)步
第二种转移无非是枚举第一次的决策拆开来计算
时间复杂度调和级数\(O(n\ln n)\)。
CO int N=1e7;
int fac[N],ifac[N];
int pn[N],p1n[N];
IN int C(int n,int m){
if(n<m) return 0;
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
int n=7.5e6;
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
ifac[n]=fpow(fac[n],mod-2);
for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
pn[0]=p1n[0]=1;
for(int i=1;i<=n;++i){
pn[i]=mul(pn[i-1],n);
p1n[i]=mul(p1n[i-1],1+mod-n);
}
int ans=0;
for(int k=0;k<n;++k){
if(k==0){
ans=add(ans,pn[n]);
continue;
}
int sum=0;
for(int i=0;i*(k+1)<=n;++i){
if(i==0){
sum=add(sum,pn[n]);
continue;
}
sum=add(sum,mul(pn[n-i*(k+1)],mul(p1n[i],C(n-i*(k+1)-1+i,i))));
sum=add(sum,mod-mul(pn[n-i*(k+1)+1],mul(p1n[i-1],C(n-i*(k+1)+i-1,i-1))));
}
ans=add(ans,add(pn[n],mod-sum));
}
printf("%d\n",ans);
return 0;
}
抽卡
Bob 喜欢抽卡。
Bob 最近入坑了一款叫“主公连结” 的抽卡游戏。游戏中共有 \(n\) 个不同的角色,编号为 \(1\sim n\)。当 Bob 获得其中的编号连续的 \(k\) 张卡时,就可以组出理论最强阵容。
当期卡池中共有 \(m\) 张不同的卡,每次抽卡,Bob 都可以等概率随机获得一张卡池中的卡。如果 Bob 抽到了一张他已经拥有的卡,那么什么事都不会发生,等于 Bob 浪费了这次抽卡机会。Bob 是个谨慎的人,他想知道,如果他不停地抽卡直到抽到编号连续的 \(k\) 张卡时停止抽卡,期望需要抽多少轮。
对于 \(100\%\) 的数据,\(1 \le m \le 200000, 1 \le a_i \le 2m, 2 \le k \le m\),保证卡池中至少存在一组可抽出的理论最强阵容(即编号连续的 \(k\) 张卡)。
题解
期望步数=期望经过的不合法的状态数=每个不合法的状态被经过的概率×期望停留次数之和。
对于那些已经合法的状态,我们可以认为它们会继续抽卡。这样做对不合法的状态被走到的概率没有影响,所以经过大小为\(|S|\)的状态概率是\(\frac{1}{\binom{m}{|S|}}\)。
在不合法的状态上期望停留的步数显然是个几何分布的期望,即\(\frac{1}{1-\frac{|S|}{m}}\)。
那么我们现在问题的关键在于,对于每个\(i\),求出大小为\(i\)的不合法的集合的个数。
卡池里的卡可以被分为不同的连续段,而不合法的状态的要求就是每个连续段内不能选出\(k\)个连续的数。注意到连续段之间的选择独立,所以可以对每个连续段分别DP,最后通过某种手段(多半是卷积)合并到一起。
设\(f(i,j)\)表示前\(i\)个数选了\(j\)个,且没有连续\(k\)个数的方案数,有\(f(0,0)=1\)。
-
当\(0\leq i\leq k-1\)时,\(f(i,j)=f(i-1,j)+f(i-1,j-1)\)。
-
当\(i=k\)时,减去前\(k\)个数都被选了的方案,\(f(i,j)=f(i-1,j)+f(i-1,j-1)-f(i-k,j-k)\)。
-
当\(k+1\leq i\leq n\)时,容斥掉\(i-k+1\sim i\)都被选了的方案,此时\(i-k\)一定没有选,\(f(i,j)=f(i-1,j)+f(i-1,j-1)-f(i-k-1,j-k)\)。
假设没有第2种转移,写成生成函数
又成了走楼梯问题,考虑求出\(F_n(x)\)。
把\(n\bmod (k+1)\)的那部分\((1+x)\)提出去,设\(m=\lfloor\frac{n}{k+1}\rfloor\),
考虑分治FFT求\(\sum_{i=0}^mA(x)^iB(x)^{m-i}C_i\)。设
加上第2种转移,也无非是多了强制第一次转移走\(k\)步贡献\(-x^k\)。
时间复杂度\(T(m)=2T(m/2)+O(mk\log(mk))=O(n\log^2n)\)。
CO int N=1<<18;
int omg[2][N],rev[N];
int fac[N],inv[N],ifac[N];
void NTT(poly&a,int dir){
int lim=a.size(),len=log2(lim);
for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<lim;i<<=1)
for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
}
if(dir){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
}
}
poly operator+(poly a,CO poly&b){
if(a.size()<b.size()) a.resize(b.size());
for(int i=0;i<(int)b.size();++i) a[i]=add(a[i],b[i]);
return a;
}
poly operator*(poly a,poly b){
int n=a.size()+b.size()-1,lim=1<<(int)ceil(log2(n));
a.resize(lim),NTT(a,0);
b.resize(lim),NTT(b,0);
for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
NTT(a,1),a.resize(n);
return a;
}
poly pow(poly a,int b){
int n=a.size()-1,lim=1<<(int)ceil(log2(n*b+1));
a.resize(lim),NTT(a,0);
for(int i=0;i<lim;++i) a[i]=fpow(a[i],b);
NTT(a,1),a.resize(n*b+1);
return a;
}
IN int C(int n,int m){
return n<m?0:mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int c[N];
poly a,b;
poly solve(int l,int r){
if(l==r) return poly{c[l]};
int mid=(l+r)>>1;
return solve(l,mid)*pow(b,r-mid)+solve(mid+1,r)*pow(a,mid+1-l);
}
poly real_main(int n,int k){
a.assign(k+1,0),a[k]=mod-1;
b=pow(poly{1,1},k+1);
for(int i=0;i*(k+1)<=n;++i) c[i]=C(n-i*(k+1)+i,i);
poly f=solve(0,n/(k+1))*pow(poly{1,1},n%(k+1));
if(n<k) return f;
for(int i=0;i*(k+1)<=n-k;++i) c[i]=C(n-k-i*(k+1)+i,i);
f=f+solve(0,(n-k)/(k+1))*pow(poly{1,1},(n-k)%(k+1))*a;
return f;
}
int main(){
omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
ifac[0]=ifac[1]=1;
for(int i=2;i<N;++i){
omg[0][i]=mul(omg[0][i-1],omg[0][1]);
omg[1][i]=mul(omg[1][i-1],omg[1][1]);
fac[i]=mul(fac[i-1],i);
inv[i]=mul(mod-mod/i,inv[mod%i]);
ifac[i]=mul(ifac[i-1],inv[i]);
}
static int a[N];
int m=read<int>(),k=read<int>();
for(int i=1;i<=m;++i) read(a[i]);
sort(a+1,a+m+1);
poly f={1};
for(int l=1,r;l<=m;l=r+1){
for(r=l;r+1<=m and a[r+1]==a[r]+1;++r);
f=f*real_main(r-l+1,k);
}
f.resize(m+1);
// cerr<<"f=";
// for(int x:f) cerr<<" "<<x;
// cerr<<endl;
int ans=0;
for(int i=0;i<m;++i)
ans=add(ans,mul(f[i],mul(fpow(C(m,i),mod-2),mul(m,inv[m-i]))));
printf("%d\n",ans);
return 0;
}