【叫不上名字反正是计数DP】 NOIP2021 T2 数列 题解
2020kanade · · 题解
人话版题意:给定一个长度为
一般长这样的计数题不是数学就是DP,这道题显然是个DP状物。
什么?数位DP?那是啥!?反正笔者不会,就整了个比较制杖的写法。
以下是一些重要的结论与转化:
-
-
关于原题中的约束:把
S 摆成二进制,很容易发现在S 的组成中,每选一个数,其下标对应的二进制位(从右向左为高,最低为0 位)上就会+1 。
由第一点,总结出一些特点:数字可以重复选,因此可以枚举某个数字选多少个,而每一个数字和选择该数字的个数(可以为
由第二点,我们可以考虑对
- 每次选数最坏情况下只会影响包括这一位上的二进制的
5 位。
至于为什么......如果对状压熟悉的话,很显然。如果看不出来的话,
因此,对于二进制状态的表示,我们可以改成两维:一维代表当前决策的那一个数对应的二进制位置和前面的
因此,状态出来了:设
继续考虑转移:选择
最终得到的总的DP方程就是把上面几个拼起来,计数的时候按照
初始状态请自行思考(提示:发送型转移的第一次转移依赖哪些状态)。时间复杂度
别忘了取模和 long long ,还有预处理组合数以及快速幂。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL md=998244353;
const int N=37,M=101;
int n,m,_k;
LL dp[M+2][N][N][N],ct[N][M+2][N][N],C[N<<1][N<<1],v[M+2],ans;
//dp[i+1][j-s][(k>>1)+s][(k&1)+l]+=dp[i][j][k][l]*v[i+1]^s*C[j][s]
inline void init_C(int _n)
{
C[0][0]=1;
for(int i=1;i<=_n+1;++i) for(int j=0;j<=i;++j) C[i][j]=((j)?(C[i-1][j]+C[i-1][j-1]+md)%md:1)%md;
}
inline int pc(int __k)
{
int ans=0;while(__k) {if(__k&1) ++ans;__k>>=1;} return ans;
}
inline LL QP(LL a,LL b)
{
LL ans=1;
while(b) {if(b&1) ans=ans*a%md;a=a*a%md,b>>=1;}
return ans%md;
}
inline LL qread()
{
LL a=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-') f*=-1;ch=getchar();}
while(ch>='0' && ch<='9') {a=a*10+(ch-'0');ch=getchar();}
return a*f;
}
inline void qwrite(LL x)
{
LL cnt=0;char w[128];
if(x<0) x*=-1,putchar('-');
while(x) {w[cnt++]=(x%10)+'0';x/=10;}
while(cnt--) putchar(w[cnt]);
}
int main()
{
n=qread(),m=qread(),_k=qread();
for(int i=0;i<=m;++i) v[i]=qread();
init_C(n);
for(int i=0;i<=n;++i) dp[0][n-i][i][0]=QP(v[0],i)*C[n][i];
for(int i=0;i<m;++i)for(int j=0;j<=n;++j) for(int k=0;k<=n;++k) for(int l=0;l<=_k;++l)
for(int s=0;s<=j;++s) dp[i+1][j-s][(k>>1)+s][(k&1)+l]=(dp[i+1][j-s][(k>>1)+s][(k&1)+l]+dp[i][j][k][l]%md*QP(v[i+1],s)%md*C[j][s]%md)%md;
for(int k=0;k<=n;++k) for(int l=0;l<=_k;++l) if(pc(k)+l<=_k) ans=(ans+dp[m][0][k][l])%md;
qwrite(ans);if(!ans) putchar(48);
return 0;
}