首页 > 其他分享 >P7961 [NOIP2021] 数列

P7961 [NOIP2021] 数列

时间:2022-08-27 21:28:19浏览次数:78  
标签:P7961 数列 个数 times num 进位 权值 NOIP2021 sim

  • 题目描述

给定整数 \(n, m, k\),和一个长度为 \(m + 1\) 的正整数数组 \(v_0, v_1, \ldots, v_m\)。

对于一个长度为 \(n\),下标从 \(1\) 开始且每个元素均不超过 \(m\) 的非负整数序列 \(\{a_i\}\),我们定义它的权值为 \(v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}\)。

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

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


首先我们有 \(20\) 分算法,枚举 \(a_i\) 计算并 check,复杂度大约是 \(O(n^m)\) 级别。

我们想拿到更多的分,观察数据范围发现有 \(50\) 分允许 \(2^m\) 的级别存在。

\(S\) 的范围是 \([n,n2^m]\),我们枚举可行的 \(S\),并判断它是否能被 \(a\) 组成,计算出贡献。可以发现这类似背包问题,我们可以选共 \(n\) 个 \(2^0\sim 2^m\) 的数组成 \(S\)。考虑倒推和记忆化搜索。因为权值可分裂,即可以表示成多个部分相乘。所以我们记 \(f[pos][sum]\) 为 \(a_i\) 的前 \(pos\) 位的组成 \(sum\) 的权值。复杂度 \(O(n2^m)\),可以得到 \(50\) 分。

计数类问题不是组合就是 dp。

考虑 dp。

我们逐步分析 dp 状态的设计。

一般来说,这种带有数列的,一般有一维为“前 \(i\) 个数”;

并且,我们发现权值与序列顺序无关,所以元素一样的序列权值一样,我们不妨让它有序。而无序的情况可以用组合数算,也可以用总体个数除以局部个数来算。

  • 组合数:已填 \(i\) 个数,接下来 \(j\) 个都填同一个值,方案数是 \(C_{n-i}^j\);
  • 阶乘:全部种类为 \(n!\),除去每个相同的块长度的阶乘即可。

让我们很烦的一个要求就是位数小于等于 \(k\),当 \(a_i\) 互不相同时,进位会造成很大的麻烦。

  • trick:进位无后效性可以采用从低位到高位的遍历方法,并且记录第 \(i\) 位之前对其的进位数。

而每一个具有相同值的块都有边界,我们考虑枚举下一个块的长度。

记 \(f[i][j][num][p][q]\) 表示已确定 \(a\) 数组中前 \(i\) 个的取值,保证取值递增,并且取值不超过为 \(j\),取值为 \(j\) 的块长为 \(num\)(为什么是“不超过“呢,因为这里的 \(num\) 可能为 \(0\),我们想要的是一种前缀的形式),即 \(a_{i-num+1}\sim a_i\) 这 \(num\) 个值都为 \(j\)。而前 \(i\) 个所有的进位,我们可以分开考虑,包括 \(a_1\sim a_{i-num}\) 的值的进位以及 \(num\) 个值为 \(j\) 的进位。我们以 \(p\) 记录若是第 \(j\) 位不进位的值会是多少,方便我们接下来直接除二进位,再用 \(q\) 记录前 \(i\) 个数组成的 \(S\) 中有多少个 \(1\)。

但是,我们发现 \(num\) 似乎不用记录,只需要枚举就行了。

简单地说,\(f[i][j][p][q]\) 表示已确定前 \(i\) 位取值不超过 \(j\),第 \(i\) 位不进位为 \(p\),第 \(j\) 位及之前共有 \(q\) 个 \(1\)。

  • 倒推不好推时,可以选用正推。

发现并不好找 \(f[i][j][p][q]\) 的组成,但我们可以考虑它对别人的贡献。

枚举 \(num\) 表示第 \(i+1\sim i+num\) 位选 \(j\),则有:

\[f[i+num][j+1][num+\frac{p}{2}][q+(num+\frac{p}{2})\bmod 2]+=f[i][j][p][q]\times v_j^{num}\times C_{n-i}^{num} \]

其中 \(i+num\) 表示已选数组 \(a\) 的前 \(i+num\) 位,并且 \(i+1\sim i+num\) 的值为 \(j+1\),此时的进位为前 \(i\) 位贡献的 \(p\) 除以二(因为进了一位),加上又选了 \(num\) 个这一位的数。对于 \(q\),只需要判断在此情况下 \(1\) 的个数有没有增加即可,加上 \((num+\frac{p}{2})\bmod 2\) 即可。

  • 明确 dp 每个状态的取值范围

\(i\) 为 \(0\sim n\):因为 \(i\) 更新 \(i+num\),而 \(num\) 可能为 \(0\);

\(j\) 为 \(0\sim m-1\):因为 \(j\) 更新 \(j+1\),所以只需要遍历到 \(j-1\) 即可;

\(p\) 为 \(0\sim 2n\):但是上界应该不会到 \(2n\)。可以考虑每一位都有 \(n\) 个选同样的值,即 \(num=n\) 的极端不可能情况。但是一定要遍历够,并且空间开大!!!!!

\(q\) 为 \(0\sim k\):上界为 \(k\) 保证 \(f[i][j][p][q]\) 是合法的再去更新;

\(num\) 为 \(0\sim n-i\):枚举选剩下的数的个数。

当然,最后的 \(S\) 可能不止 \(m\) 位,但是添加的数最多只到第 \(m\) 位,后面不会被修改,所以后面可以不进行 dp,而是直接计算。

答案就是:

\[\sum_{p=0}^{2n}\sum_{q=0}^kf[n][m][p][q]\times[q+\rm{popcnt}(\frac{p}{2})\leq k] \]

  • 别忘了取模哦!

时间复杂度 \(O(n^3mk)\)。

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}

void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10^48);
}

const int N=31,M=101,mod=998244353;
int n,m,k,v[M][N],f[N][M][N<<1][N],ans,C[N][N];

int popcnt(int x){
	int num=0;
	while(x){
		x-=(x&-x),num++;
	}
	return num;
}

signed main(){
	n=read(),m=read(),k=read();
	C[0][0]=C[1][0]=C[1][1]=1;
	for(int i=2;i<=n;++i){C[i][0]=1;
		for(int j=1;j<=i;++j){
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
		}
	}
	for(int i=0;i<=m;++i){
		v[i][1]=read();v[i][0]=1;
		for(int j=2;j<=n;++j)v[i][j]=v[i][j-1]*v[i][1]%mod;
	}
	for(int i=0;i<=n;++i)f[i][0][i][i&1]=v[0][i]*C[n][i]%mod;
	for(int i=0;i<=n;++i){
		for(int j=0;j<m;++j){
			for(int p=0;p<=2*n;++p){
				for(int q=0;q<=k;++q){
					for(int num=0;num<=n-i;++num){
						f[i+num][j+1][num+p/2][q+(num+p/2)%2]=(f[i+num][j+1][num+p/2][q+(num+p/2)%2]+f[i][j][p][q]*v[j+1][num]%mod*C[n-i][num]%mod)%mod;
					}
				}
			}
		}
	}
	for(int p=0;p<=2*n;++p){
		for(int q=0;q<=k;++q){
			if(q+popcnt(p/2)<=k){
				ans=(ans+f[n][m][p][q])%mod;
			}
		}
	}
	print(ans);
	return 0;
}

标签:P7961,数列,个数,times,num,进位,权值,NOIP2021,sim
From: https://www.cnblogs.com/Daidly/p/16631499.html

相关文章

  • 划分数列(ybtoj递推练习题1)
    题目描述给定一个长度为n的数列 ,要求划分最少的段数,使得每一段要么单调不降,要么单调不升。输入格式第一行一个整数 。接下来n个数表示数列 。......
  • 「学习笔记」不动点法求数列通项
    前言不动点法求数列通项是怎么回事呢?不动点法相信大家都很熟悉,但是不动点法求数列通项是怎么回事呢,下面就让小编带大家一起了解吧不动点法求数列通项,其实就是数列通项可......
  • 1084 外观数列——20分
    外观数列是指具有以下特点的整数序列:d,d1,d111,d113,d11231,d112213111,...它从不等于1的数字d开始,序列的第n+1项是对第n项的描述。比如第2项表示第1......
  • 斐波那契数列前1000项
    {1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,57......
  • 剑指 Offer 10- I. 斐波那契数列
    一、题目写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项(即F(N))。斐波那契数列的定义如下:F(0)=0,  F(1) =1F(N)=F(N-1)+F(N-2),其中N>1.斐......
  • 使用JS 递归函数 输出 斐波那契数列 (return 返回值使用时的注意点 )
      functionaee(i){  if(i==0){    return0;  }  if(i==1){    return1;  }  if(i>=2){ ***//......
  • 【笔记】斐波那契数列
    定义\[\largeF_n=\begin{cases}0&n=0\\1&n=1\\F_{n-2}+F_{n-1}&\operatorname{otherwise}.\end{cases}\]通项公式\[\largeF_n=\frac{\left(\frac{1+\sqrt5......
  • 17:菲波那契数列
    描述菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整数k,要求菲波那契数列中第k个数是多少。输入输入一行,包......
  • OpenJudge1.5.17 菲波那契数列
    17:斐波那契数列总时间限制:1000ms内存限制:65536kB描述菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整......
  • 17:斐波那契数列
    描述菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整数k,要求菲波那契数列中第k个数是多少。输入输入一行,包含......