#4155. 咱们去烧菜吧

题意

内存限制:512 MiB
时间限制:1000 ms

你有 m m m 种物品,第 i i i 种物品的大小为 a i a_i ai,数量为 b i b_i bi b i = 0 b_i=0 bi=0 表示有无限个)。

你还有 n n n 个背包,体积分别为 1 1 1 n n n,现在你很想知道用这些物品填满某个背包的方案数。

为了满足你的好奇心,你决定把填满每个背包的方案数都算一遍。

因为你其实只是闲得无聊,所以你只想知道方案数对 998244353 998244353 998244353 7 × 17 × 2 23 + 1 7\times 17\times 2^{23}+1 7×17×223+1,一个质数)取模后的值。

0 &lt; n , m ≤ 1 0 5 , 0 ≤ a i ≤ 110000 , 0 ≤ b i ≤ 1 0 6 0&lt; n,m\le 10^5, 0\le a_i\le 110000,0\le b_i\le 10^6 0<n,m105,0ai110000,0bi106

题解

把第 i i i 个物品的生成函数写出,设 b i &gt; 0 b_i&gt;0 bi>0,即 F i ( x ) = ∑ j = 0 b i x a i × j = 1 − x a i × ( b i + 1 ) 1 − x a i F_i(x)=\sum_{j=0}^{b_i} x^{a_i \times j}=\frac{1-x^{a_i \times (b_i+1)}}{1-x^{a_i}} Fi(x)=j=0bixai×j=1xai1xai×(bi+1)

那么当 b i = 0 b_i=0 bi=0 时, F i ( x ) = ∑ j = 0 ∞ x a i × j = 1 1 − x a i F_i(x)=\sum_{j=0}^{\infty} x^{a_i \times j}=\frac{1}{1-x^{a_i}} Fi(x)=j=0xai×j=1xai1

则最后的答案的生成函数 G ( x ) = ∏ i = 1 n F i ( x ) G(x)=\prod_{i=1}^nF_i(x) G(x)=i=1nFi(x)

两边取对数,即 ln ⁡ G ( x ) = ln ⁡ ( ∏ i = 1 n F i ( x ) ) = ∑ i = 1 n ln ⁡ F i ( x ) = ∑ i = 1 n ( ln ⁡ ( 1 − x a i × ( b i + 1 ) ) − ln ⁡ ( 1 − x a i ) ) \ln G(x)=\ln (\prod_{i=1}^nF_i(x))=\sum_{i=1}^n \ln F_i(x)=\sum_{i=1}^n(\ln (1-x^{a_i \times (b_i+1)})-\ln(1-x^{a_i})) lnG(x)=ln(i=1nFi(x))=i=1nlnFi(x)=i=1n(ln(1xai×(bi+1))ln(1xai))

又知道 ln ⁡ ( 1 − x k ) = ∑ i = 1 ∞ x i × k i \ln(1-x^k)=\sum_{i=1}^{\infty} \frac{x^{i \times k}}{i} ln(1xk)=i=1ixi×k

所以对于每个 k k k ,都会对 x i × k x^{i \times k} xi×k 的系数造成影响,所以这一部分可以 O ( n ln ⁡ n ) O(n\ln n) O(nlnn) 预处理

所以最后做个多项式 e x p exp exp 即可

#include <bits/stdc++.h>
#define I inline
using namespace std;
const int P=998244353,N=5e5+5;
int n,m,a[N],b[N],r[N],t,p,G[2]={3,332748118};
int A[N],B[N],C[N],D[N],E[N],F[N],jc[N],ny[N];
I int X(int x){if (x>=P) x-=P;return x;}
I int inv(int x){return 1ll*jc[x-1]*ny[x]%P;}
I int K(int x,int y){
	int A=1;
	for (;y;y>>=1,x=1ll*x*x%P)
		if (y&1) A=1ll*A*x%P;
	return A;
}
I void Ntt(int *g,bool o){
	for (int i=0;i<t;i++)
		if (i<r[i]) swap(g[i],g[r[i]]);
	for (int wn,i=1;i<t;i<<=1){
		wn=K(G[o],(P-1)/(i<<1));
		for (int x,y,j=0;j<t;j+=(i<<1))
			for (int w=1,k=0;k<i;k++,w=1ll*w*wn%P)
				x=g[j+k],y=1ll*w*g[i+j+k]%P,
				g[j+k]=X(x+y),g[i+j+k]=X(x-y+P);
	}
	if (o)
		for (int i=0,v=K(t,P-2);i<t;i++)
			g[i]=1ll*v*g[i]%P;
}
I void dao(int *a,int *b,int l){
	for (int i=1;i<l;i++)
		b[i-1]=1ll*i*a[i]%P;
	b[l]=b[l-1]=0;
}
I void jifen(int *a,int *b,int l){
	for (int i=1;i<l;i++)
		b[i]=1ll*a[i-1]*inv(i)%P;
	b[0]=0;
}
I void pre(int l){
	for (t=1,p=0;t<l+l;t<<=1,p++);
	for (int i=0;i<t;i++)
		r[i]=(r[i>>1]>>1)|((i&1)<<(p-1));
}
I void getinv(int *a,int *b,int l){
	if (l==1){
		b[0]=K(a[0],P-2);return;
	}
	getinv(a,b,(l+1)>>1);
	for (int i=0;i<l;i++)
		A[i]=a[i],B[i]=b[i];
	pre(l);Ntt(A,0);Ntt(B,0);
	for (int i=0;i<t;i++)
		A[i]=1ll*A[i]*B[i]%P*B[i]%P;
	Ntt(A,1);for (int i=0;i<l;i++)
		b[i]=X(X(b[i]+b[i])-A[i]+P);
	for (int i=0;i<t;i++) A[i]=B[i]=0;
}
I void getln(int *a,int *b,int l){
	dao(a,C,l);getinv(a,D,l);
	pre(l);Ntt(C,0);Ntt(D,0);
	for (int i=0;i<t;i++)
		C[i]=1ll*C[i]*D[i]%P;
	Ntt(C,1);jifen(C,b,l);
	for (int i=0;i<t;i++) C[i]=D[i]=0;
}
I void getexp(int *a,int *b,int l){
	if (l==1){b[0]=1;return;}
	getexp(a,b,(l+1)>>1);
	for (int i=0;i<l;i++) E[i]=b[i];
	getln(b,F,l);
	for (int i=0;i<l;i++)
		F[i]=X(a[i]-F[i]+P);
	F[0]=X(F[0]+1);pre(l);
	Ntt(F,0);Ntt(E,0);
	for (int i=0;i<t;i++)
		E[i]=1ll*E[i]*F[i]%P;
	Ntt(E,1);
	for (int i=0;i<l;i++) b[i]=E[i];
	for (int i=0;i<t;i++) E[i]=F[i]=0;
}
int main(){
	scanf("%d%d",&n,&m);
	for (int x,y,i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		if (x<=n) a[x]++;
		if (y && 1ll*x*(y+1)<=1ll*n)
			a[x*(y+1)]--;
	}
	jc[0]=1;
	for (int i=1;i<=n;i++)
		jc[i]=1ll*i*jc[i-1]%P;
	ny[n]=K(jc[n],P-2);
	for (int i=n;i;i--)
		ny[i-1]=1ll*i*ny[i]%P;
	for (int i=1;i<=n;i++){
		if (!a[i]) continue;
		if (a[i]<0) a[i]+=P;
		for (int j=1;j*i<=n;j++)
			b[i*j]=X(b[i*j]+1ll*a[i]*inv(j)%P);
		a[i]=0;
	}
	getexp(b,a,n+1);
	for (int i=1;i<=n;i++)
		printf("%d\n",a[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值