NOIP 2021 数列 (dp+二进制相关)(读者 rp +++++++)
序
先剧透下,这道题复杂度 O ( n 4 m ) \Omicron(n^4m) O(n4m)
快来看看吧
题意
给定 n , m , k n,m,k n,m,k 和一个长度为 m + 1 ( 0 ∼ m ) m+1(0 \sim m) m+1(0∼m) 的正整数数组 v v v
对于一个长度为 n ( 1 ∼ n ) n(1 \sim n) n(1∼n) 的序列 a a a ( 0 ≤ a i ≤ m 0 \le a_i \le m 0≤ai≤m),权值为 ∏ i = 1 n v a i \prod_{i=1}^{n} v_{a_i} ∏i=1nvai
当序列 a a a 满足 p o p c o u n t ( ∑ i = 1 n 2 a i ) ≤ k popcount(\sum_{i=1}^{n} 2^{a_i}) \le k popcount(∑i=1n2ai)≤k 时1, a a a 为一个合法序列。
求所有合法序列的权值和 % 998244353 \%998244353 %998244353 。
解法
很有意思的一道 dp
先钦定 a a a 为升序,这样好操作一点,最后再乘上方案数就好。
设 f i , j , k , l f_{i,j,k,l} fi,j,k,l 表示选到 a i a_i ai ( a i a_i ai 已经选完),当前最高位为 j j j (即最大选了 j − 1 j-1 j−1 ),选了 k k k 个,状态中有 l l l 个 1 1 1 。
初始化 f 0 , 0 , 0 , 0 = 1 f_{0,0,0,0}=1 f0,0,0,0=1
首先枚举 i , j , k , l i,j,k,l i,j,k,l ,然后再枚举一维 p ( 0 ∼ n − i ) p(0 \sim n-i) p(0∼n−i) ,意为往后面再放 p p p 个 j j j
那么 i + 1 ∼ i + p i+1 \sim i+p i+1∼i+p 就全部放好了;最高位也由 j j j 变成了 j + 1 j+1 j+1 (选得最大的数由 j − 1 j-1 j−1 变成了 j j j );由于上一位选了 k k k 个,加上当前选的 p p p 个,那进位后就变成了 ⌊ k + p 2 ⌋ \lfloor \frac{k+p}{2} \rfloor ⌊2k+p⌋ ;同时如果有余数,状态中为 1 1 1 的就 + 1 +1 +1 ,所以为 l + ( k + p ) % 2 l+(k+p)\%2 l+(k+p)%2 。
而因为选了 p p p 个 j j j ,所以贡献也要加上 p p p 个 j j j 的,所以在输入时预处理,把 v v v 数组改成二维, v i , j v_{i,j} vi,j 表示 i i i 选了 j j j 个的贡献。
那么状态转移方程即为
f[i+p][j+1][(k+p)>>1][l+(k+p)%2]+=f[i][j][k][l]*v[j][p];
不对不对,还要乘上方案数呢?
上面卡掉,重新来:
f[i+p][j+1][(k+p)>>1][l+(k+p)%2]+=f[i][j][k][l]*v[j][p]*c[i+p][p];
c c c 就是组合数, c i , j c_{i,j} ci,j 表示 C i j C_i^j Cij ,可以提前 O ( n 2 ) \Omicron(n^2) O(n2) 预处理(应该都会,不再赘述,不会的看代码)
那么最后的答案为 ∑ i = 0 n ∑ j = 0 , p o p c o u n t ( i ) + j ≤ K K f n , m + 1 , i , j \sum_{i=0}^{n} \sum_{j=0,popcount(i)+j \le K}^{K} f_{n,m+1,i,j} ∑i=0n∑j=0,popcount(i)+j≤KKfn,m+1,i,j
代码
#include<cstdio>
#define ll long long int
using namespace std;
const ll Md=998244353;
const int N=30,M=100;
ll c[N+10][N+10],v[M+10][N+10],f[N+10][M+10][N+10][N+10];
int cnt(int x)
{
int s=0;
while(x)
{
s+=x%2;
x>>=1;
}
return s;
}
int main()
{
int n,m,K;
scanf("%d%d%d",&n,&m,&K);
for(int i=0;i<=n;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j]+c[i-1][j-1])%Md;
}
}
for(int i=0;i<=m;i++)
{
v[i][0]=1;
scanf("%lld",&v[i][1]);
for(int j=2;j<=n;j++)
{
v[i][j]=(v[i][j-1]*v[i][1])%Md;
}
}
f[0][0][0][0]=1;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=(i>>1);k++)
{
for(int l=0;l<=K;l++)
{
for(int p=0;p<=n-i;p++)
{
f[i+p][j+1][(k+p)>>1][l+(k+p)%2]=(f[i+p][j+1][(k+p)>>1][l+(k+p)%2]+((f[i][j][k][l]*v[j][p])%Md*c[i+p][p])%Md)%Md;
}
}
}
}
}
ll ans=0;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=K;j++)
{
if((cnt(i)+j)<=K)
{
ans=(ans+f[n][m+1][i][j])%Md;
}
}
}
printf("%lld",ans);
return 0;
}
后记
呼
终于打完了,推状态转移方程那部分自认为还是打的蛮详细蛮用心的
大家也要用心看吖
不要忘记一键三连哦
谢谢🍟(用意念请大家吃薯条)
p o p c o u n t ( i ) popcount(i) popcount(i) 表示 i i i 在二进制下 1 1 1 的个数 ↩︎