Loading

[NOIP2021] 数列

前言

\(\texttt{E}\color{red}{\texttt{ricQian}}\) 场切的题,我到现在才来补。

感觉自己做了挺多 \(dp\) 题了,可以尝试自己做一下。

大意

给你一个长 \(m\) 的数组 \(v\),要求满足 \(\operatorname{popcount}(\sum2^{a_i})\le k\)\(a\) 数列的权值和。定义一个数列的权值为 \(\prod v_{a_i}\)

Sol

看到数据范围很小,考虑高维 \(dp\) 或者状压。显然状压没有前途,考虑设高维的 \(dp\)

然后你考虑题目实际上是让你对于一个空白的二进制位,每次可以把一位加一,求最后 \(1\) 的个数不超过 \(k\) 个。我们必须要维护的状态显然有当前已经填了几个 \(1\),然后当前 \(1\) 的个数是多少。这样我们当前加了一个位置之后,所需要知道的是能进几位的问题对吧。所以我们还需要维护当前进了几位,也就是前面填了的 \(1\) 有几个是因为进位而消失的。

这样我们有一个初步的想法就是令 \(dp[i][j]\) 表示当前填了 \(i\)\(1\),当前有 \(j\)\(1\) 的权值,这样不见了的 \(1\) 就是 \(i-j\) 个。

考虑转移,就是枚举当前填的 \(1\) 是在哪个位置上的,然后我们枚举它进的位数,这样容易知道上一个状态的 \(1\) 的个数。然后你发现这点状态是不够的,你枚举当前进了几位,就必须要知道有连续几个 \(1\) 在当前位的答案。于是你发现这样的做法好像没有办法继续维护了。

那么既然是和位数相关,那我不妨从低位向高位去考虑。那由于这样会对计数没有顺序的过程产生影响,所以我们不妨顺序考虑二进制中的位,然后看当前这位填到 \(a\) 的哪一位去。就是说,我们把 \(dp[i][j][k]\) 表示当前考虑到二进制第 \(i\) 位,当前有 \(j\)\(1\),并且已经填了 \(a\) 中的 \(k\) 个数的权值和。接着考虑从当前状态转移出去,就是你考虑当前位放几个 \(1\),假设是 \(p\) 个,那么对 \(j\) 的影响是加上了 \(p\%2\),当然还要加上上一位对当前位的贡献,所以还需要加上上一位的进位。

因此我们最终确定大炮状态是 \(dp[i][j][k][d]\) 表示考虑二进制第 \(i\) 位的时候,此前已经有了 \(j\)\(1\),并且填了 \(a\)\(k\) 个数,且上一位对当前位的进位为 \(p\) 的所有方案的权值和。那么转移就比较好想了,就是:

\[dp[i][j][k][d]\times {n-k\choose p}\times v_{i}^p\to dp[i+1][j+(p+d)\%2][k+p][\lfloor\dfrac{p+d}{2}\rfloor] \]

然后暴力转移,最终答案就是 \(dp[m+1][0\sim k][n][0\sim n]\) 的和。

哦注意最后那个 \(\operatorname{popcount}(d)+j\le k\) 的时候才可以统计入答案。

然后就做完了。

Code

// Problem: P7961 [NOIP2021] 数列
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7961
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Time: 2022-04-13 21:42:27

#include<bits/stdc++.h>
#define int long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb push_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
using namespace std;
const int MOD=998244353;
int ksm(int a,int p){
	int ret=1;while(p){
		if(p&1) ret=ret*a%MOD;
		a=a*a%MOD; p>>=1;
	}return ret;
}
int C[35][35];
void init(){
	C[0][0]=1;
	rep(i,1,33){
		C[i][0]=1; rep(j,1,i)
		C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}
int dp[110][110][35][35],v[110];
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	init();
	int n,m,K;cin>>n>>m>>K;
	rep(i,0,m) cin>>v[i];
	rep(p,0,n) dp[0][p][p][0]=C[n][p]*ksm(v[0],p)%MOD;
	rep(i,0,m) rep(j,0,i) rep(k,0,n) rep(d,0,k){
		// pt(i);pt(j);pt(k);pts(d);
		for(int p=0;p+k<=n;p++){
			// pt(dp[i][j][k][d]);pts(C[n-k][p]);
			// pts(v[i]);
			(dp[i+1][j+(p+d)%2][k+p][(p+d)/2]+=dp[i][j][k][d]*C[n-k][p]%MOD*ksm(v[i],p)%MOD)%=MOD;
		}
	}int ans=0;
	rep(j,0,K) rep(d,0,n){
		if(j+__builtin_popcount(d)>K) continue;
		ans=(ans+dp[m+1][j][n][d])%MOD;
	}cout<<ans<<'\n';
	return 0;
}
posted @ 2022-04-14 19:04  ZCETHAN  阅读(112)  评论(0编辑  收藏  举报