首页 > 其他分享 >NOIP2021 数列

NOIP2021 数列

时间:2024-11-13 15:43:35浏览次数:1  
标签:数列 int ll ret times num NOIP2021 mod

NOIP2021 数列

算法一

最暴力的爆搜,枚举每个位置所有填值的情况,时间复杂度 \(O(n^m)\)。可以拿到20分。

算法二

没那么暴力的爆搜,注意到填数的具体位置不重要,只关系每种数的出现次数。

考虑暴力枚举每个数出现了多少次,记数字 \(i\) 出现了 \(x_i\) 次。所求即为下面这个不定方程解的个数:

\[x_0 + x_1 + x2 + \dots + x_m = n \]

利用隔板法可以分析出,时间复杂度为 \(O(C_{n + m}^{m})\)。

至于一种方案的真正出现次数,要乘上:

\[\binom{n}{x_0} \times \binom{n - x_0}{x_1} \times \dots \times \binom{x_m}{x_m} \]

可以拿35~45分。

算法三

对于 \(m \le 12\),考虑状压dp

记 \(f[i][j][s]\) 表示长度为 \(i\),用了 \(0 \sim j\) 这些值(可以不用),权值和为 \(s\),方案数。

有转移

\[f[i][j][s] = \sum_{0 \le k \le i} f[i - k][j- 1][s - 2^j \times k] \times\binom{i}{k} \]

时间复杂度 \(O(2^mn^3m)\)。可以拿50分。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int mod = 998244353;
const int N = 105;
int n, m, lim, ans = 0;
int v[N], f[32][15][122900], c[32][32]; // f[i][j][s] : 长度为 i, 用了 0 ~ j 这些值, 权值和为 s 
inline ll quickmod(ll x, int y){
	ll ret = 1;
	while(y){
		if(y & 1) ret = ret * x % mod;
		x = x * x % mod, y >>= 1;
	}
	return ret;
}
inline int popcount(int x){ return __builtin_popcount(x); }
signed main(){
//	freopen("sequence2.in","r",stdin);
//	freopen("sequence.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m >> lim;
	F(i, 0, m) cin >> v[i];
	F(i, 0, n) c[i][i] = c[i][0] = 1;
	F(i, 2, n) F(j, 1, i - 1) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	F(j, 0, m) F(i, 1, n) f[i][j][(1 << j) * i] = quickmod(v[j], i); //长度为 i,全用一种数j 
	F(j, 0, m){ // 用了 0 ~ j 这些值 
		F(i, 1, n){ //长度 
			F(s, 0, (1 << j) * i){ // 权值和
				F(k, 0, i - 1){ // 用了 k 个 j 
					f[i][j][s] += quickmod(v[j], k) * f[i - k][j - 1][s - ((1 << j) * k)] % mod * c[i][k] % mod;
					if(f[i][j][s] >= mod) f[i][j][s] -= mod;
				}
			}
		}
	}
	F(s, 0, (1 << m) * n) if(popcount(s) <= lim) (ans += f[n][m][s]) %= mod;	
	cout << ans << '\n';
	return fflush(0), 0;
}

算法四

位数太多存不下,考虑数位dp

还是得枚举目前最大使用的值 \(j\),注意到如果 \(0 \sim j\) 的个数都已经确定,那么后面无论怎么填数,\(0 \sim j\) 位都不会再变化了。

所以我们从低位到高位dp,转移时维护进位就可以。

关于进位,一次可能不止进一位,但我们多开一维状态记录最高位填了多少,我们最后再处理这些进位,这样就很方便了。

另外可能一位都进不了,但进位这一位的原理本质是个桶,所以可以直接当成最高位填了 0 个来进位。

记 \(f[i][j][k][l]\) 表示长度为 \(i\),当前进位最高位到了第 \(j\) 位(因此填数只填到了 \(j - 1\)),第 \(j\) 位有 \(k\) 个,已经有 \(l\) 个位是 1,序列方案数。

有转移(采用刷表法,假设新填 \(num\) 个 \(j\))

\[f[i][j + 1][(k + num) / 2][l + (k + num) \bmod 2] += f[i][j][k][l] \times v_j^{num} \times \binom{i + num}{num} \]

答案:

\[ans = \sum_{k, l}f[n][m + 1][k][l] \times [popcount(k) + l \le K] \]

\(K\) 是读入的。

初始化:\(f[0][0][0][0] = 1\)。

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

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 35, M = 105;
const int mod = 998244353;
ll f[N][M][N][M], v[M], c[N][N], ans = 0;
int n, m, lim;
int popcount(int x){
	return __builtin_popcount(x);
}
int quickmod(ll x, int y){
	ll ret = 1;
	while(y){
		if(y & 1) ret = ret * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ret;
}
signed main(){
//	freopen("sequence.in","r",stdin);
//	freopen("sequence.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m >> lim;
	F(i, 0, m) cin >> v[i];
	F(i, 0, n) c[i][0] = c[i][i] = 1;
	F(i, 2, n) F(j, 1, i - 1) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
//	F(i, 0, n) { F(j, 0, i) cout << c[i][j] << ' '; cout << '\n';}return 0;
	f[0][0][0][0] = 1;
	F(i, 0, n) F(j, 0, m) F(k, 0, i) F(l, 0, i) F(num, 0, n - i) (f[i + num][j + 1][(k + num) / 2][l + (k + num) % 2] += f[i][j][k][l] * quickmod(v[j], num) % mod * c[i + num][num] % mod) %= mod;
	F(k, 0, n) F(l, 0, lim) if(popcount(k) + l <= lim) {
		ans += f[n][m + 1][k][l];
		if(ans >= mod) ans -= mod;
	}
	cout << ans << '\n';
	return fflush(0), 0;
}

标签:数列,int,ll,ret,times,num,NOIP2021,mod
From: https://www.cnblogs.com/superl61/p/18544089

相关文章

  • 数列分段(二分)
    [数列分段SectionII]题目描述对于给定的一个长度为\(N\)的正整数数列\(A_{1\simN}\),现要将其分成\(M\)(\(M\leqN\))段,并要求每段连续,且每段和的最大值最小。关于最大值最小:例如一数列\(4\2\4\5\1\)要分成\(3\)段。将其如下分段:\[[4\2][4\5][1]\]第一段和为......
  • Python程序:计算特定数列之和
    题目要求编写一个Python程序,计算数列$s=a+aa+aaa+aaaa+\ldots$的和,其中$a$是一个数字,数列中每个数都是由$a$重复组成,且重复次数逐渐增加。用户可以通过键盘控制数列中相加的数的个数。解题思路为了计算这个数列的和,我们需要首先理解数列的构成。每个数都......
  • 在C语言中用函数求fibonacci(斐波那契)数列前n项的和
    1.功能用函数求fibonacci数列前n项的和。2.说明fibonacci数列为数列的第一项值为1,第二项值也为1,从第三项开始,每一项均为其前面相邻两项的和。3.题目例如:当n=28时,运行结果:832039。请编写sum函数。#include<stdio.h>//函数sum用于计算斐波那契数列前n项的和longsum......
  • python3.5-IDLE中斐波那契数列程序实现
    斐波那契数列F(n)定义:F(0)=0,F(1)=1,……,F(n)=F(n-2)+F(n-1),其中n≥2(简单总结,从第3个数起,斐波那契数列中每个数都是前两个数之和)代码实现:1)采用迭代方式实现:deffibonacci_iterative(n):ifn<=0:return0elifn==1:return1......
  • 2287: 【例28.3】 数列分段
    include<bits/stdc++.h>usingnamespacestd;intn,m,sum,num;intmain(){cin>>n>>m;for(inti=1;i<=n;i++){inte;cin>>e;if(num+e>m){sum++;num=e;}else{num+=e;}}cout<<sum+1;return0;}反思:这段代......
  • B2032 等差数列末项计算
    题目描述等差数列是一个很有趣的数列,它的任何相邻两项的差相等。现在给出一个等差数列的前两项 a_1,a_2a1​,a2​ 的值,求第 nn 项是多少。输入格式一行,包含三个整数 a_1,a_2,na1​,a2​,n(-100\lea_1,a_2\le100−100≤a1​,a2​≤100,0<n\le10000<n≤1000。)输出......
  • 有序数列合并C++
    将两个数列合并为一个数列,并从小到大排序题目描述输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。输入描述输入包含三行,第一行包含两个正整数n,m(1≤n,m≤100),用空格分隔。n表示第二行第一个升序序列中数字的个数,m表示第三行第二个升序序列中数字的个数。......
  • 《上海市计算机学会竞赛平台2024年8月月赛丙组题目T1 统计得分 T2 等差数列的素性 T3
    T1统计得分内存限制: 256 Mb时间限制: 1000 ms题目描述在一场知识竞赛中,选手答对一题得 11 分,答错不得分且要倒扣 11 分,但扣分不能让分数小于 00。给定一个由 Y 及 N 构成的字符序列,答对记为 Y,答错记为 N。选手一开始从 00 分开始,请输出选手最后的得分......
  • 【动态规划之斐波那契数列模型】——累加递推型动态规划
    文章目录第N个泰波那契数列面试题08.01.三步问题使用最小花费爬楼梯解码问题第N个泰波那契数列解题思路:泰波那契数列的第N项定义为前面三项之和,即T0=0,T1=1,T2=1,从T3开始,每一项都等于前三项的和。要找到第N项,可以使用动态规划逐步求解每个值直到TN......
  • 差分与等差数列问题
    利用差分的思想解决多次对数组区间加相同数,或者加一个等差数列最好思路:从目标数列往前推两次前缀和,反推差分数组应该怎么加  #include<bits/stdc++.h>#defineintlonglongusingnamespacestd;intn,m,l,r,s,e,d,maxv,ans;inta[10000005],sum[10000005];sig......