安吉D1-G
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G本文
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
P8366 [LNOI2022] 题
题目描述
给定长度为 、值域为 的整数序列 .你需要首先将 中的每个 替换为 中的任意一个整数,得到序列 ,然后给出 个长度为 的整数序列 ,使得
- ,;
- ,;
- , 是 的一个排列且逆序对数为奇数.
认为两个方案本质不同当且仅当序列 不同或存在 (,)不同,求以上操作的本质不同的方案数,对 取模.
输入格式
本题有多组测试数据.输入的第一行包含一个正整数 表示测试数据组数.
对于每组测试数据,第一行一个整数 ,接下来一行一个长度为 的字符串描述序列 .
输出格式
对于每组测试数据输出一行一个整数表示方案数对 取模的结果.
输入输出样例 #1
输入 #1
511231100100023213212000001输出 #1
013660说明/提示
【样例解释 #1】
前三组测试数据中 ,故 .
对于第一组测试数据,只能有 ,而 的逆序对数为 不合法,故不存在方案.
对于第二组测试数据, 不合法,而 时 的逆序对数为 合法,故存在一个方案.
对于第三组测试数据,取 ,, 可以得到三个合法方案.
对于第四组测试数据,,有如下六种方案:
-
,
-
,
-
,
-
,
-
,
-
,
【数据范围】
对于所有测试数据,,,字符串 的长度为 且仅由 构成.
| 测试点编号 | 特殊性质 | |
|---|---|---|
| 无 | ||
| 无 | ||
| 无 | ||
| A | ||
| 无 | ||
| 无 | ||
| A | ||
| 无 | ||
| 无 | ||
| 无 |
特殊性质 A:字符串 由全 的字符串构成.
【提示】
请注意程序的空间消耗.
思路
观察到数据范围中 很小,所以可以选择使用高次的 DP.
不难发现,一个序列是 的一个排列且逆序对数为奇数,当且仅当这个序列属于 .
遍历整个序列,若当前元素为 1(或 0),则可以是:
- 单独的一个
- 和前面的 组合成
- 和前面的 组合成
若当前元素为 2 或 3 时同理.
依照这个设计状态:dp[i][a][b][c][d][e][f],用于表示考虑到第 个元素时:
- 前面剩余单独的 有
a个 - 前面剩余单独的 有
b个 - 前面剩余单独的 有
c个 - 前面剩余的 有
d个 - 前面剩余的 有
e个 - 前面剩余的 有
f个 的方案数量.
初始时,dp[0][0][0][0][0][0][0] = 1 表示前 0 个元素时,肯定什么都没有剩余,此时仅一种方案.
接着,遍历整个序列,若当前元素为 2(或 0),则 dp[i][a][b][c][d][e][f] 可以增加:
dp[i-1][a][b-1][c][d][e][f],增加了一个单独的dp[i-1][a][b][c+1][d][e][f-1] * (c+1),花费任意一个 (由于有 个 ,所以有 种选法),增加了一个dp[i-1][a][b][c][d][e+1][f] * (e+1),花费一个 (同理,有 种选法),组成了一个 ,这个组合合法,消去.
若当前元素为 1 或 3 时同理.
最终状态为 dp[3*n][0][0][0][0][0][0].
观察得出 dp[i][...] 只与 dp[i-1][...] 有关,因此使用滚动数组优化.
什么是滚动数组?
滚动数组,是一种节省空间的做法,一般来说,如果 dp[i][...] 只与 dp[i-x][...](x 为常数,且 x 通常为 1)有关时,可以使用滚动数组.
以 x=1 为例,在计算完 dp[0][...] 和 dp[1][...] 之后,由于之后的计算不再需要用到 dp[0][...],因此,可以将 dp[2][...] 存放在 dp[0][...] 的位置,从而达到节省空间的目的.
在实现时,常常定义 cur = i & 1 和 pre = (i-1) & 1,利用 dp[cur][...] 来代替 dp[i][...],dp[pre][...] 来代替 dp[i-1][...].
由于算出来的是划分数,而题目要求的是排列,所以还要乘 .
#include <bits/stdc++.h>#define allow_one(x) ((x) <= i && (x) <= n)typedef long long ll;using namespace std;const int N = 60;const int MOD = 1e9 + 7;int n, A[N];ll fac[N];ll calFac(int x) { if (fac[x] == 0) { fac[x] = (1ll * calFac(x - 1) * x) % MOD; } return fac[x];}// dp[one][two][three][oneTwo][oneThree][twoThree]// {1,3,2},{2,1,3},{3,2,1} 是合法的int dp[2][21][21][21][21][21][21];void solve() { memset(dp, 0, sizeof(dp)); cin >> n; string str; cin >> str; for (int i = 0; i < str.size(); i++) { A[i + 1] = str[i] - '0'; } dp[0][0][0][0][0][0][0] = 1; for (int i = 1; i <= 3 * n; i++) { int pre = (i - 1) & 1, now = i & 1; for (int a = 0; allow_one(a); a++) for (int b = 0; allow_one(a + b); b++) for (int c = 0; allow_one(a + b + c); c++) for (int d = 0; a + b + c + d <= n && a + b + c + 2 * d <= i; d++) for (int e = 0; e <= n && a + b + c + 2 * d + 2 * e <= i && a + b + c + d + e <= n; e++) for (int f = 0; f <= n && a + b + c + 2 * d + 2 * e + 2 * f <= i && a + b + c + d + e + f <= n; f++) { ll cur = 0; // 清零 if (A[i] == 1 || A[i] == 0) { // 新增一个 1 if (a - 1 >= 0) cur = (cur + dp[pre][a - 1][b][c][d][e][f]) % MOD; // 与已有的 2 结合成 {2,1},消耗 b,增加 d if (d - 1 >= 0) cur = (cur + 1ll * dp[pre][a][b + 1][c] [d - 1][e][f] * (b + 1)) % MOD; // 与已有的 {3,2} 完成,消耗 f if (f < n) // 保证 f+1 不越界 cur = (cur + 1ll * dp[pre][a][b][c][d][e][f + 1] * (f + 1)) % MOD; } if (A[i] == 2 || A[i] == 0) { // 新增一个 2 if (b - 1 >= 0) cur = (cur + dp[pre][a][b - 1][c][d][e][f]) % MOD; // 与已有的 3 结合成 {3,2},消耗 c,增加 f if (f - 1 >= 0) cur = (cur + 1ll * dp[pre][a][b][c + 1][d] [e][f - 1] * (c + 1)) % MOD; // 与已有的 {1,3} 完成,消耗 e if (e < n) cur = (cur + 1ll * dp[pre][a][b][c][d][e + 1][f] * (e + 1)) % MOD; } if (A[i] == 3 || A[i] == 0) { // 新增一个 3 if (c - 1 >= 0) cur = (cur + dp[pre][a][b][c - 1][d][e][f]) % MOD; // 与已有的 1 结合成 {1,3},消耗 a,增加 e if (e - 1 >= 0) cur = (cur + 1ll * dp[pre][a + 1][b][c][d] [e - 1][f] * (a + 1)) % MOD; // 与已有的 {2,1} 完成,消耗 d if (d < n) cur = (cur + 1ll * dp[pre][a][b][c][d + 1][e][f] * (d + 1)) % MOD; } dp[now][a][b][c][d][e][f] = cur % MOD; } } cout << (dp[(3 * n) & 1][0][0][0][0][0][0] * calFac(n)) % MOD << endl;}
signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); fac[0] = 1; int T; cin >> T; while (T--) { solve(); } return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


