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)。
-
当(1leq ileq k)时,(g(i)=n imes g(i-1))。
-
当(i=k+1)时,恰好有(n)种方案不合法,(g(i)=n imes g(i-1)-n imes g(0))。
-
当(k+2leq ileq n)时,容斥掉(i-ksim i)都是同色的的方案。那么由于(g(i-1))的限制,(i-k-1)和(i-k)的颜色必须不同。所以(g(i)=n imes g(i-1)-(n-1) imes 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
",ans);
return 0;
}
假设没有第2种转移,考虑这个递推式的组合意义。相当于走楼梯,往上走(1)步的代价是乘(n),走(k+1)步的代价是乘(-(n-1))。
那么我们枚举一共做了多少次走(k+1)步
第二种转移无非是枚举第一次的决策拆开来计算
时间复杂度调和级数(O(nln 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
",ans);
return 0;
}
抽卡
Bob 喜欢抽卡。
Bob 最近入坑了一款叫“主公连结” 的抽卡游戏。游戏中共有 (n) 个不同的角色,编号为 (1sim 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}{inom{m}{|S|}})。
在不合法的状态上期望停留的步数显然是个几何分布的期望,即(frac{1}{1-frac{|S|}{m}})。
那么我们现在问题的关键在于,对于每个(i),求出大小为(i)的不合法的集合的个数。
卡池里的卡可以被分为不同的连续段,而不合法的状态的要求就是每个连续段内不能选出(k)个连续的数。注意到连续段之间的选择独立,所以可以对每个连续段分别DP,最后通过某种手段(多半是卷积)合并到一起。
设(f(i,j))表示前(i)个数选了(j)个,且没有连续(k)个数的方案数,有(f(0,0)=1)。
-
当(0leq ileq 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+1leq ileq n)时,容斥掉(i-k+1sim 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))。
把(nmod (k+1))的那部分((1+x))提出去,设(m=lfloorfrac{n}{k+1} floor),
考虑分治FFT求(sum_{i=0}^mA(x)^iB(x)^{m-i}C_i)。设
加上第2种转移,也无非是多了强制第一次转移走(k)步贡献(-x^k)。
时间复杂度(T(m)=2T(m/2)+O(mklog(mk))=O(nlog^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
",ans);
return 0;
}