NOIP2021-数列
NOIP2021-数列
显然是个 DP 题,这个题 做法是不是垫底了啊。
状态设计
首先发现这个序列的元素的顺序不会影响权值和 值,所以我们可以使序列为空,然后将值填入序列,计算对权值和 的贡献,对这个过程进行 DP。
因为对于等于 的 ,它可以影响 的第 位及其后面的位,所以我们从小往大讨论 的值。以它作为阶段从小往大考虑,可以避免后效性。
因为确定了小于 的所有 后,可以确定 前 位的情况,所以我们对于填完小于 的值的状态只需要记录 前 位的 的数量,可以忽略具体哪些位是 。
当然,小于 的 对第 位后面的位置是有影响的,所以我们记录一维状态表示当前的 。
现在还有一个状态被忽略了,也就是当前序列已经有多少位置不是空位,这个状态让我们知道有多少空位可以赋值。
设计 表示已经填了小于 的所有 值,总计 个, 的前 位有 个 ,。
转移
先写方程:
可以发现这里目标状态只枚举了 ,, 三维,剩下一维是由转移所枚举的 , 决定的。我不知道如何定义每个状态的转移复杂度,但是总复杂度一定是枚举的这五个变量的范围之积。
接下来解释这个转移, 枚举的是数字 选择的数量, 枚举的是起始状态进位到 的数字,所以可以当作是在 的基础上,额外选择了 个 ,但是不花费空位,也不消耗权值,仅对 产生影响。
这样前面的二项式系数的意义就很显然了,我们起始状态有 个空位,我们从中选择 个填上 的方案数就是 。
表示原来每个方案的基础上加 个 ,都会使方案的权值乘 。
表示的是本次填入 对 贡献的 和原来已经存在的 ,它们的和除以 的结果,也就是 。
的意义是填入和合并得到的 ,对 中前 位 的数量的贡献,显然,如果 是奇数,就会使得 的第 位是 。
代码
代码是考场代码增删注释得到的,所以码风保守。
为了防止变量名冲突,上面提到的 , 在代码中分别写成 和 ,题目中的 用 来代替。
为了防止数组越界, 表示第 个阶段的 DP 值,也就是填了数字 的 DP 值。因此需要在输入时把 变成 。
const unsigned long long Mod(998244353);
unsigned long long Ans(0), C[35][35], a[105][35];
unsigned f[105][35][35][35], Pop[105];
unsigned n, m, t;
unsigned A, B;
unsigned Cnt(0);
signed main() {
n = RD(), m = RD() + 1, t = RD();
for (unsigned i(1); i <= m; ++i) {
a[i][0] = 1, a[i][1] = RD();
for (unsigned j(2); j <= n; ++j)
a[i][j] = a[i][j - 1] * a[i][1] % Mod;//预处理每个值有不同个数的权值 (也就是预处理 a 的幂)
}
for (unsigned i(1); i <= 102; ++i) Pop[i] = Pop[i >> 1] + (i & 1);//预处理 Popcount
C[0][0] = 1;
for (unsigned i(1); i <= n; ++i) {
C[i][0] = 1;
for (unsigned j(1); j <= n; ++j) {
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];//预处理二项式系数
if(C[i][j] >= Mod) C[i][j] -= Mod;
}
}
for (unsigned i(0); i <= m; ++i) f[i][0][0][0] = 1;//初始化
for (unsigned i(1); i <= m; ++i) {
for (unsigned j(1); j <= n; ++j) {
for (unsigned k(0); k <= t; ++k) {
for (unsigned jj(0); jj <= j; ++jj) {
for (unsigned ll(0); ll <= n; ++ll) {
if((ll + jj) & 1) {
if(!k) continue;//防止越界
f[i][j][k][(ll + jj) >> 1] = ((f[i][j][k][(ll + jj) >> 1] + (f[i - 1][j - jj][k - 1][ll] * C[n - j + jj][jj] % Mod) * a[i][jj]) % Mod);
} else {
f[i][j][k][(ll + jj) >> 1] = ((f[i][j][k][(ll + jj) >> 1] + (f[i - 1][j - jj][k][ll] * C[n - j + jj][jj] % Mod) * a[i][jj]) % Mod);
}
}
}
}
}
}
for (unsigned i(0); i <= t; ++i) {
for (unsigned j(0); j <= n; ++j) {
if(Pop[j] + i <= t) {//第 n 位后面的 1 的数量和前 n 位的 1 的数量和
Ans += f[m][n][i][j];//统计答案
if(Ans >= Mod) Ans -= Mod;
}
}
}
printf("%u\n", Ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)