P7961 [NOIP2021] 数列

此题看上去直接暴力dfs,你就可以拿到20pts 

但是优化一下,设dp数组dp[pos][num]表示搜索到第pos位,总和为num时的余下位的权值进行一下记忆化搜索,就可以水到50pts

#include<bits/stdc++.h>
#define re register
#define int long long
#define inl inline
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int mod=998244353;
inl int count(int x){
	int res=0;
	while(x){
		res++;
		x-=(x&-x); 
	}
	return res;
}
int n,m,k;
int v[110],a[1000],ans;
int dp[35][120010];
inl int dfs(int pos,int num){
	if(pos==n+1) return count(num)<=k;
	if(dp[pos][num]!=-1) return dp[pos][num];
	int res=0;
	for(re int i=0;i<=m;i++){
		a[pos]=i;
		res=(res+dfs(pos+1,num+(1<<i))*v[i]%mod)%mod;
	}
	return dp[pos][num]=res;
}
signed main(){
	n=read(),m=read(),k=read();
	memset(dp,-1,sizeof(dp));
	for(re int i=0;i<=m;i++) v[i]=read();
	printf("%lld",dfs(1,0));
	return 0;
}

下面开始讲正解,其实是dp,设dp[i][j][p][q],表示前i位填了j个1,i+1位堆积了p个1,前i位共有q个1的答案

可以得到转移:dp[i][j][p][q]=dp[i-1][j-d][a][b]*(val[i]^d)*\binom{n-j+d}{d}

d表示第i位填的1的个数,val[i]^d表示d个a[i]带来的贡献

\binom{n-j+d}{d}表示a的组合情况

ab满足下列等式:

(a+d)/2=p

b+[(a+d)%2]=q

那么答案就为ans=\sum dp[m][n][i][0-(k-count(i)]

Code

#include<bits/stdc++.h>
#define re register
#define int long long
#define inl inline
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int mod=998244353;
inl int count(int x){
	int res=0;
	while(x){
		res++;
		x-=(x&-x); 
	}
	return res;
}
int n,m,k;
int v[110],a[1000],ans;
int dp[110][35][35][35];//放到i位,j个1,i+1堆了p个1,到i有q个合法1 
inl int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		b>>=1;a=a*a%mod;
	}
	return res;
}
int c[35][35];
inl void init(){
	for(re int i=0;i<=n;i++){
		c[i][0]=1;
		for(re int j=1;j<=i;j++){
			c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
		}
	}
	return;
}
//int dp[35][120010];
/*inl int dfs(int pos,int num){
	if(pos==n+1) return count(num)<=k;
	if(dp[pos][num]!=-1) return dp[pos][num];
	int res=0;
	for(re int i=0;i<=m;i++){
		a[pos]=i;
		res=(res+dfs(pos+1,num+(1<<i))*v[i]%mod)%mod;
	}
	return dp[pos][num]=res;
}*/
signed main(){
	n=read(),m=read(),k=read();
	for(re int i=0;i<=m;i++) v[i]=read();
	init();
	for(re int i=0;i<=n;i++){
		dp[0][i][i/2][i&1]=ksm(v[0],i)*c[n][i]%mod;
	}
	for(re int i=1;i<=m;i++){
		for(re int j=0;j<=n;j++){
			for(re int p=0;p<=j/2;p++){
				for(re int q=0;q<=j;q++){
					for(re int d=0;d<=j;d++){
						int a=p*2-d;
						int b=q-(a+d)%2;
						if(a>=0&&b>=0){
							dp[i][j][p][q]+=dp[i-1][j-d][a][b]*ksm(v[i],d)%mod*c[n-(j-d)][d]%mod;
						}
						dp[i][j][p][q]%=mod;
						a++;
						b=q-(a+d)%2;
						if(a>=0&&b>=0){
							dp[i][j][p][q]+=dp[i-1][j-d][a][b]*ksm(v[i],d)%mod*c[n-(j-d)][d]%mod;
						}
						dp[i][j][p][q]%=mod;
					}
				}
			}
		}
	}
	for(re int i=0;i<=n;i++){
		for(re int j=0;j<=k-count(i);j++){
			ans=(ans+dp[m][n][i][j])%mod;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值