[luogu p7961] [NOIP2021] 数列
P7961 [NOIP2021] 数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给定整数
对于一个长度为
当这样的序列
计算所有合法序列
-
; -
; -
。
与
这题的样例容易给人启发:一个
我们发现,当两个序列
因此考虑把研究对象从序列
为什么会想到转化成
数组?我是通过可以考虑一个序列 重排得到的序列种数,发现会跟 有关而想到的,一会就会提到。 其实,
数组是序列 的一种无序化的体现。这也是本题我们第一步要转化的(好像很多题解都忽略了这点……直接想到?)
为了搞定转化,有两个目标:
- 求出一个数组
可以对应多少个序列 ,这个值我们记为 ; - 将
和 采用数组 表示。
第一个目标的答案:
第二个目标的答案:
我们知道,所有合法的
搞一下发现是
dfs
至于
我们把原来数据的表格拓展一下:
测试点 | 是否可过 | ||||
---|---|---|---|---|---|
是 | |||||
是 | |||||
否 | |||||
否 | |||||
是 | |||||
否 | |||||
否 | |||||
否 |
观察到,可以通过 差评
考虑正解,计数问题要么是 dp 要么是组合数学,接下来应该是往 dp 的方向走了。数据范围可以给我们提示,我们足够大胆地设置 dp 状态。
接下来的突破口很容易发现,就是
紧接着有一个比较头疼的问题,那就是进位。二进制中的每一位只能容下一个
不过,冷静下来我们思考,会发现进位并不是一种玄学,它是有理有据的:第
到这里可以开始考虑 dp 方程了。怎么设状态?
我们一定是从
为什么这里我都用的是
那么我们的意图应该是满足什么条件呢?容易发现有两条:
; 。
那么考虑多设两维:
这里其实是个经典套路:遇到看似棘手的限制,就考虑设成 dp 中新的一维。
然后又考虑到转移
现在就变成:
- 满足
的和为 ; - 满足
的第 位到第 位 的数量为 ; - 第
位收到的进位即将是 ;
所有可能的
上面那个条件都有个“第
下一个状态是什么呢?肯定是
那么
那么就可以写出转移方程了:
边界是
接下来考虑如何统计答案。
显然答案对应的
但我们发现
但事实上这个细节很好处理。
所以答案就是所有满足
不要忘记最后答案还要乘个
还有一个细节:
其实也挺显然的:
/*
* @Author: crab-in-the-northeast
* @Date: 2022-09-12 12:55:18
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-09-12 13:33:29
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int maxn = 35;
const int maxm = 105;
const int maxk = maxn;
const int maxx = maxn >> 1;
const int mod = 998244353;
int v[maxm][maxn];
int f[maxm][maxn][maxk][maxx];
int iac[maxn];
inline int qpow(int p, int q = mod - 2) {
int ans = 1;
while (q) {
if (q & 1)
(ans *= p) %= mod;
(p *= p) %= mod;
q >>= 1;
}
return ans;
}
inline int popcnt(int x) {
int ans = 0;
while (x) {
if (x & 1)
++ans;
x >>= 1;
}
return ans;
}
signed main() {
int n = read(), m = read(), k = read();
for (int i = 0; i <= m; ++i) {
int val = read();
v[i][1] = val;
v[i][0] = 1;
for (int j = 2; j <= n; ++j)
v[i][j] = (v[i][j - 1] * val) % mod;
}
int z = 1;
for (int i = 2; i <= n; ++i)
(z *= i) %= mod;
iac[n] = qpow(z);
for (int i = n - 1; i >= 0; --i)
iac[i] = (iac[i + 1] * (i + 1)) % mod;
f[0][0][0][0] = 1;
for (int i = 0; i <= m; ++i)
for (int j = 0; j <= n; ++j)
for (int p = 0; p <= k; ++p)
for (int x = 0; x <= (j >> 1); ++x)
for (int t = 0; t <= n - j; ++t)
(f[i + 1][j + t][p + ((t + x) & 1)][(t + x) >> 1]
+= f[i][j][p][x] * v[i][t] % mod * iac[t] % mod) %= mod;
int ans = 0;
for (int p = 0; p <= k; ++p)
for (int x = 0; x <= (n >> 1); ++x)
if (p + popcnt(x) <= k)
(ans += f[m + 1][n][p][x]) %= mod;
printf("%lld\n", ans * z % mod);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】