一个让单身狗们崩溃的题……
题目大意:
有N件物品,一共取D次,一次取的必须少于M件,问共有多少种取法。(每个物品相同,有多测,对998244353取模)
题解:
30%算法(N,D<=20,M<=10)
简单的DP。
设f[i][j]为取了i次,共取了j件物品的方案数,则有如下状态转移方程:
f[i][j]=∑k<jk=max(j-m,0)f[i-1][k]
初状态f[0][0]=1,末状态为f[d][n]。
时间复杂度O(NMD)。
100%算法(N,M<=2000,D<=1012)
可以看出D大的吓人,我们不可能枚举D。
我们首先会想到矩阵快速幂,但是此题的矩阵并不特殊,短时间找规律很难,不易优化成n2级别,但n3的复杂度又承受不起。
我们发现,相对于D,N很小,也就是说最多有N天能取到东西,剩下的天数如同虚设。
我们只考虑这N天内的情况,如上设出f[i][j],则状态转移方程为:
f[i][j]=∑k<jk=max(j-m,1)f[i-1][k]
但是和上面不同,此时的N,M是103级别,不能像上面一样转移,用前缀和优化可以解决。
下面就是总方案数的求法。
在D天里,有可能有N/(M-1)~N天当中取到物品,所以总方案数为:
ans=∑i<=ni=1f[i][n]*C(D,i)
我们发现大组合数不好求,于是打算采用消项法,D!和(D-i)!可以消走,剩下约N项可O(N)求出,分母的i!可以求逆元解决。
值得注意的是,相乘时因数较大,需要需要现将因数取模再相乘,否则会乘暴long long,得到光荣的WA30,只能说数据太强了。
单次复杂度O(NM)。
Code:
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #define LL long long
5 using namespace std;
6 const LL mod=998244353;
7 const LL N=2010;
8 int n,m;
9 LL d,dp[N][N],f[N],jc[N],inv[N];
10 LL qpow(LL x,LL y)
11 {
12 LL ans=1;
13 while(y>0){
14 if(y&1) ans=ans*x%mod;
15 x=x*x%mod;
16 y>>=1;
17 }
18 return ans;
19 }
20 void pre()
21 {
22 jc[0]=inv[0]=jc[1]=inv[1]=1;
23 for(int i=2;i<=2000;i++){
24 jc[i]=jc[i-1]*(LL)(i)%mod;
25 inv[i]=qpow(jc[i],mod-2);
26 }
27 }
28 int main()
29 {
30 pre();
31 while(1){
32 scanf("%d%lld%d",&n,&d,&m);
33 if(n==0&&d==0&&m==0) break;
34 memset(dp,0,sizeof(dp));
35 dp[0][0]=1;
36 for(int i=1;i<=n;i++){
37 f[0]=dp[i-1][0];
38 for(int j=1;j<=n;j++) f[j]=(f[j-1]+dp[i-1][j])%mod;
39 for(int j=1;j<=n;j++){
40 dp[i][j]=f[j-1];
41 if(j-m>=0) dp[i][j]-=f[j-m];
42 dp[i][j]=(dp[i][j]%mod+mod)%mod;
43 }
44 }
45 LL ans=0;
46 for(int i=1;i<=n;i++){
47 LL now=dp[i][n];
48 for(LL j=d-(LL)(i-1);j<=d;j++) now=j%mod*now%mod;
49 now=now*inv[i]%mod;
50 ans=(ans+now)%mod;
51 }
52 printf("%lld
",ans);
53 }
54 return 0;
55 }
帅哥美女们能否顺手点个推荐。