counting

Time Limit: 5000 ms Memory Limit: 512 MB

Description

  数学老师走啦,英语老师来上课啦
  他的性格与众不同,又因为大家都是理科班的学生
  他希望大家在数字母的过程中领悟英语的快乐

  他用(m)种字母进行排列组合,
  得到了所有不同的,长度为(n)的字符串
  (不需要所有字母都出现在字符串中)
  对于每个字符串(s)
  定义(C(s))(s)中出现次数最多的字母的出现次数
  那么问题来了
  所有的这些字符集大小为(m),长度为(n)的字符串中
  (C(s)=k)的有多少个呢

Input

  一行三个整数(n,m,k),分别表示字符集,长度和要求的(C(s))

Output

  输出一行表示结果
  答案对(998244353)取模

Sample Input

3 2 2

Sample Output

6

HINT

  数据保证(k≤n)
  对于10%的数据,(1≤n,m≤8)
  对于30%的数据,(1≤n,m≤200)
  对于50%的数据,(1≤n,m≤1000)
  对于100%的数据,(1≤n,m≤50000)

样例解释:

  假设样例中的两个字母为(a,b)
  则满足条件的有(aab,aba,abb,baa,bab,bba)六个


Solution

  先考虑一下大力dp,(f[i][j][k])表示确定完前(i)个字母的位置,占用了(s)中的(j)个位置,且此时(C(s)<=k)的方案数

[egin{aligned} f[i][j][k]&=sumlimits_{l=0}^{k}inom j l f[i-1][j-l][k]\ &=sumlimits_{l=0}^{k}frac{j!}{l!(j-l)!}f[i-1][j-l][k]\ &=j!sumlimits_{l=0}^{k}frac{1}{l!}cdot frac{f[i-1][j-1][k]}{(j-l)!}\ \ frac{f[i][j][k]}{j!}&=sumlimits_{l=0}^{k}frac{1}{l!}cdot frac{f[i-1][j-1][k]}{(j-l)!}\ end{aligned} ]

  那么最后的答案应该是(f[m][n][k]-f[m][n][k-1])

  仔细观察一下这个dp式子,会发现这个dp其实跟(k)没有太大的关系(影响的只是枚举范围),而(i)这一维通过转移时候的继承是可以直接省掉的,然后又发现这个是个卷积的形式,那就大力构造一下多项式然后乘一波咯

  我们定义(F_i(x)=sumlimits_{j=0}^{k}frac{f[i][j][k]}{j!}x^j)(G(x)=sumlimits_{j=0}^{k}frac{1}{j!}x^j),那么上面式子的(frac{f[i][j][k]}{j!})就可以看做(F_i(x))(j)次项的系数(生成函数的意味。。?),其他的部分同理,我们可以得到这样的式子:

[F_i(x)=F_{i-1}(x)G(x) ]

  最后的(f[m][n][k])就是:(F_m(x))(n)次项的系数*(n!)(f[m][n][k-1])的计算同理,只要稍微改变一下(k)的值就好了,所以下面只讨论(f[m][n][k])的求法:

  重新看回(F_i(x))的那个递推式,不难发现其实(F_m(x)=(F_0(x)G(x))^m)(一路推上来的嘛)

  那就是做一个快速幂卷积就好了,注意一下每次乘完之后超过(n)的部分要赋成(0)

  (初始化的话因为只有(f[0][0][k]=1),所以(F_0(x)=1)

  

  代码大概长这个样子(NTT忘记swap这个错能犯一万年。。。菜菜发抖qwq)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define G 3
#define vct vector<int>
using namespace std;
const int MAXN=5*(1e4)+10,MOD=998244353;
int ksm(int x,int y);
namespace NTT{/*{{{*/
	int W[MAXN*4][2],rev[MAXN*4];
	int len,invlen,invg;
	void init(){
		invg=ksm(G,MOD-2);
		for (int i=0;i<=17;++i){
			W[1<<i][0]=ksm(G,(MOD-1)/(1<<i));
			W[1<<i][1]=ksm(invg,(MOD-1)/(1<<i));
		}
	}
	void prework(int n){
		int bit=0;
		for (len=1;len<n;len<<=1,++bit);
		rev[0]=0;
		for (int i=1;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		invlen=ksm(len,MOD-2);
	}
	void ntt(int *a,int op){
		int w,w_n,u,t;
		for (int i=0;i<len;++i) 
			if (rev[i]>i) swap(a[i],a[rev[i]]);
		for (int step=2;step<=len;step<<=1){
			w_n=op==-1?W[step][0]:W[step][1];
			for (int st=0;st<len;st+=step){//< !!
				w=1;
				for (int i=0;i<(step>>1);++i){
					t=1LL*a[st+i+(step>>1)]*w%MOD;
					u=a[st+i];
					a[st+i]=(1LL*u+t)%MOD;
					a[st+i+(step>>1)]=(1LL*u+MOD-t)%MOD;
					w=1LL*w*w_n%MOD;
				}
			}
		}
		if (op==1) return;
		for (int i=0;i<len;++i)
			a[i]=1LL*a[i]*invlen%MOD;
	}
}/*}}}*/
int fac[MAXN],invfac[MAXN];
int base[MAXN*4],ret[MAXN*4];
int n,m,k;
void prework(int n);
void ksm_solve(int *a,int n,int y,int *ret);//n=len m=num k=appear times
int solve(int n,int m,int k);

int main(){
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
#endif
	scanf("%d%d%d",&n,&m,&k);
	prework(n);
	int ansk,ansk1;
	ansk1=solve(n,m,k-1);
	ansk=solve(n,m,k);
	printf("%d
",(1LL*ansk+MOD-ansk1)%MOD);
}

int ksm(int x,int y){
	int ret=1,base=x;
	for (;y;y>>=1,base=1LL*base*base%MOD)
		if (y&1) ret=1LL*ret*base%MOD;
	return ret;
}

void prework(int n){
	NTT::init();
	NTT::prework((n+1)*2);
	fac[0]=1;
	for (int i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%MOD;
	invfac[n]=ksm(fac[n],MOD-2);
	for (int i=n-1;i>=0;--i) invfac[i]=1LL*invfac[i+1]*(i+1)%MOD;
}

int solve(int n,int m,int k){
	memset(base,0,sizeof(base));
	memset(ret,0,sizeof(ret));
	for (int i=0;i<=k;++i) base[i]=invfac[i];
	ksm_solve(base,n,m,ret);
	return 1LL*ret[n]*fac[n]%MOD;
}

void ksm_solve(int *a,int n,int y,int *ret){
	ret[0]=1;
	while (y){
		NTT::ntt(a,1);
		if (y&1){
			NTT::ntt(ret,1);
			for (int i=0;i<NTT::len;++i) ret[i]=1LL*ret[i]*a[i]%MOD;
			NTT::ntt(ret,-1);
			for (int i=n+1;i<NTT::len;++i) ret[i]=0;
		}
		for (int i=0;i<NTT::len;++i) a[i]=1LL*a[i]*a[i]%MOD;
		NTT::ntt(a,-1);
		for (int i=n+1;i<NTT::len;++i) a[i]=0;
		y>>=1;
	}
}
原文地址:https://www.cnblogs.com/yoyoball/p/8907306.html