NOIP2021 T2 数列

  传送门:NOIP2021 T2 数列

题目大意

  给定整数 n , m , k n, m, k n,m,k,和一个长度为 m + 1 m + 1 m+1 的正整数数组 v 0 , v 1 , … , v m v_0, v_1, \ldots, v_m v0,v1,,vm

  对于一个长度为 n n n,下标从 1 1 1 开始且每个元素均不超过 m m m 的非负整数序列 { a i } \{a_i\} {ai},我们定义它的权值为 v a 1 × v a 2 × ⋯ × v a n v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n} va1×va2××van

  当这样的序列 { a i } \{a_i\} {ai} 满足整数 S = 2 a 1 + 2 a 2 + ⋯ + 2 a n S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n} S=2a1+2a2++2an 的二进制表示中 1 1 1 的个数不超过 k k k 时,我们认为 { a i } \{a_i\} {ai} 是一个合法序列。

  计算所有合法序列 { a i } \{a_i\} {ai} 的权值和对 998244353 998244353 998244353 取模的结果。

分析

  我还依稀记得考场上打的最暴力的暴力… 测下来一分没有qwq

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MOD 998244353
#define MAXN 202

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int k = 0;
int v[MAXN] = { 0 };
int n = 0; int m = 0;

inline int lowbit(int x){
	return x & -x;
}

int ans = 0;
int a[MAXN] = { 0 };
void work(){
	int S = 0;
	for(int i = 1; i <= n; i++) S += pow(2, a[i]);
	int cnt1 = 0;
	while(S){
		S -= lowbit(S); cnt1++;
		if(cnt1 > k) return;
	}
	int temp = 1;
	for(int i = 1; i <= n; i++)
		temp = (1ll * temp * v[a[i]]) % MOD;
	ans = (1ll * ans + temp) % MOD;
}

void dfs(int now){
	if(now > n){ work(); return; }
	for(int i = 0; i <= m; i++){
		a[now] = i;
		dfs(now + 1);
		a[now] = 0;
	}
}

int main(){
	n =  in; m = in; k = in;
	for(int i = 0; i <= m; i++) v[i] = in;
	dfs(1);
	cout << ans << endl;
	return 0;
}

  好了,现在来看看正常的解法。

  看看这个数据范围, n , m n, m n,m 都比较小,所以应该不是计数,那就考虑 d p dp dp。设 f i , j , k , l f_{i, j, k, l} fi,j,k,l 表示当前处理第 i i i 位,已经用了 j j j 个数,前 i i i 位中一共有 k k k 个 “ 1 1 1”,且在第 i i i 为进位了 l l l 次。

  那么根据这些信息我们再枚举当前在 i i i 位置上放 x x x 个 “ 1 1 1”。我们就能知道第 i + 1 i + 1 i+1 位是 0 0 0 还是 1 1 1。具体来说,我们在 i i i 位进位了 l l l 次,那么 i + 1 i + 1 i+1 位就进位了 l 2 \frac l2 2l 次。并且我们又放了 x x x 1 1 1,那么第 i + 1 i + 1 i+1 位就总共进位 ⌊ l + x 2 ⌋ \lfloor \frac {l+x}2 \rfloor 2l+x 次。那么我们就能从:

f i , j , k , l f_{i, j, k, l} fi,j,k,l

  转移到:

f i + 1 , j + x , k + ( ⌊ l + x 2 ⌋ ∧ 1 ) , ⌊ l + x 2 ⌋ f_{i + 1, j + x, k + (\lfloor \frac {l + x}2 \rfloor \land 1), \lfloor\frac {l + x}2 \rfloor} fi+1,j+x,k+(⌊2l+x1),2l+x

  就是这样转移:

f i , j , k , l → f i + 1 , j + x , k + ( ⌊ l + x 2 ⌋ ∧ 1 ) , ⌊ l + x 2 ⌋ f_{i, j, k, l} \to f_{i + 1, j + x, k + (\lfloor \frac {l + x}2 \rfloor \land 1), \lfloor\frac {l + x}2 \rfloor} fi,j,k,lfi+1,j+x,k+(⌊2l+x1),2l+x

  显然这是不能直接硬往上加的,前面还有些系数。首先显然的一个系数就是 v i x v_{i}^x vix 这个就是我在第 i i i 位加 x x x i i i 对答案的贡献,然后还有一个系数就是方案数,也就是说还剩 n − j n - j nj 个数里面选出 x x x 个为 i i i 的方案数就是 ( n − j x ) \begin{pmatrix} n - j \\ x \end{pmatrix} (njx)。所以转移方程应该就是:

f i + 1 , j + x , k + ( ⌊ l + x 2 ⌋ ∧ 1 ) , ⌊ l 2 ⌋ + x = ∑ f i , j , k , l × v i x × ( n − j x ) f_{i + 1, j + x, k + (\lfloor \frac {l + x}2 \rfloor \land 1), \lfloor\frac l2 \rfloor + x} = \sum f_{i, j, k, l} \times v_i^x \times \begin{pmatrix} n - j \\ x \end{pmatrix} fi+1,j+x,k+(⌊2l+x1),2l+x=fi,j,k,l×vix×(njx)

  然后我们预处理一下组合数就可以快乐的 d p dp dp 啦。答案就是:

a n s = ∑ i = 0 n ∑ j = 0 k [ j + p o p c n t ( i ) ≤ k ] f m , n , j , i ans = \sum_{i = 0}^n\sum_{j = 0}^k [j + popcnt(i) \leq k]f_{m, n, j, i} ans=i=0nj=0k[j+popcnt(i)k]fm,n,j,i

  完结撒花!!!

代码

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 50
#define MAXM 505
#define MOD 998244353

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int K = 0;
int n = 0; int m = 0;
int v[MAXM][MAXN] = { 0 };                     // v[i][j] = v_i^j
int c[MAXN][MAXN] = { 0 };
int f[MAXM][MAXN][MAXN][MAXN] = { 0 };

int popcnt(int x){
	int ans = 0;
	while(x) ans += (x & 1), x >>= 1;
	return ans; 
} 

int main(){
	n = in; m = in; K = in; int ans = 0;
	for(int i = 0; i <= m; i++){
		v[i][0] = 1; v[i][1] = in;
		for(int j = 2; j <= n; j++)
			v[i][j] = 1ll * v[i][j - 1] * v[i][1] % MOD;
	}
	c[0][0] = 1;
	for(int i = 1; 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]) % MOD;
	}
	f[0][0][0][0] = 1;
	for(int i = 0; i <= m; i++)
		for(int j = 0; j <= n; j++)
			for(int k = 0; k <= K; k++)
				for(int l = 0; l <= n >> 1; l++)
					for(int x = 0; x <= n - j; x++)
						f[i + 1][j + x][k + (l + x & 1)][l + x >> 1] = (f[i + 1][j + x][k + (l + x & 1)][l + x >> 1] + 1ll * f[i][j][k][l] * v[i][x] % MOD * c[n - j][x] % MOD) % MOD;
     for(int j = 0; j <= K; j++)
	 	for(int i = 0; i <= n >> 1; i++)
	 		if(j + popcnt(i) <= K) ans = (ans + f[m + 1][n][j][i]) % MOD;
    cout << ans << '\n';
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值