视频加载失败

安吉D1-G

1910 字
10 分钟
安吉D1-G
原题呈现

P8366 [LNOI2022] 题#

题目描述#

给定长度为 3n3 n、值域为 [0,3][0, 3] 的整数序列 S=s1s2s3nS = s_1 s_2 \cdots s_{3 n}.你需要首先将 SS 中的每个 00 替换为 [1,3][1, 3] 中的任意一个整数,得到序列 T=t1t2t3nT = t_1 t_2 \cdots t_{3 n},然后给出 nn 个长度为 33 的整数序列 {ai,1,ai,2,ai,3}1in{\{ a_{i, 1}, a_{i, 2}, a_{i, 3} \}}_{1 \le i \le n},使得

  • 1in\forall 1 \le i \le n1ai,1<ai,2<ai,33n1 \le a_{i, 1} < a_{i, 2} < a_{i, 3} \le 3 n
  • (i1,j1)(i2,j2)\forall (i_1, j_1) \ne (i_2, j_2)ai1,j1ai2,j2a_{i_1, j_1} \ne a_{i_2, j_2}
  • 1in\forall 1 \le i \le n{tai,1,tai,2,tai,3}\{ t_{a_{i, 1}}, t_{a_{i, 2}}, t_{a_{i, 3}} \}{1,2,3}\{ 1, 2, 3 \} 的一个排列且逆序对数为奇数.

认为两个方案本质不同当且仅当序列 TT 不同或存在 ai,ja_{i, j}1in1 \le i \le n1j31 \le j \le 3)不同,求以上操作的本质不同的方案数,对 (109+7)({10}^9 + 7) 取模.

输入格式#

本题有多组测试数据.输入的第一行包含一个正整数 CC 表示测试数据组数.

对于每组测试数据,第一行一个整数 nn,接下来一行一个长度为 3n3 n 的字符串描述序列 SS

输出格式#

对于每组测试数据输出一行一个整数表示方案数对 (109+7)({10}^9 + 7) 取模的结果.

输入输出样例 #1#

输入 #1#

5
1
123
1
100
1
000
2
321321
2
000001

输出 #1#

0
1
3
6
60

说明/提示#

【样例解释 #1】

前三组测试数据中 n=1n = 1,故 {a1,1,a1,2,a1,3}={1,2,3}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 1, 2, 3 \}

对于第一组测试数据,只能有 T=123T = 123,而 {1,2,3}\{ 1, 2, 3 \} 的逆序对数为 00 不合法,故不存在方案.

对于第二组测试数据,T=123T = 123 不合法,而 T=132T = 132{1,3,2}\{ 1, 3, 2 \} 的逆序对数为 11 合法,故存在一个方案.

对于第三组测试数据,取 T=132T = 132T=213T = 213T=321T = 321 可以得到三个合法方案.

对于第四组测试数据,T=321321T = 321321,有如下六种方案:

  • {a1,1,a1,2,a1,3}={1,2,3}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 1, 2, 3 \}{a2,1,a2,2,a2,3}={4,5,6}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 4, 5, 6 \}

  • {a1,1,a1,2,a1,3}={4,5,6}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 4, 5, 6 \}{a2,1,a2,2,a2,3}={1,2,3}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 1, 2, 3 \}

  • {a1,1,a1,2,a1,3}={1,2,6}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 1, 2, 6 \}{a2,1,a2,2,a2,3}={3,4,5}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 3, 4, 5 \}

  • {a1,1,a1,2,a1,3}={3,4,5}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 3, 4, 5 \}{a2,1,a2,2,a2,3}={1,2,6}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 1, 2, 6 \}

  • {a1,1,a1,2,a1,3}={1,5,6}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 1, 5, 6 \}{a2,1,a2,2,a2,3}={2,3,4}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 2, 3, 4 \}

  • {a1,1,a1,2,a1,3}={2,3,4}\{ a_{1, 1}, a_{1, 2}, a_{1, 3} \} = \{ 2, 3, 4 \}{a2,1,a2,2,a2,3}={1,5,6}\{ a_{2, 1}, a_{2, 2}, a_{2, 3} \} = \{ 1, 5, 6 \}

【数据范围】

对于所有测试数据,1C51 \le C \le 51n191 \le n \le 19,字符串 SS 的长度为 3n3 n 且仅由 0,1,2,30, 1, 2, 3 构成.

测试点编号nn \le特殊性质
1111
2222
3333
4455A
5577
661010
771313A
881616
991818
10101919

特殊性质 A:字符串 SS 由全 00 的字符串构成.

【提示】

请注意程序的空间消耗.

思路#

观察到数据范围中 nn 很小,所以可以选择使用高次的 DP.

不难发现,一个序列是 {1,2,3}\{ 1, 2, 3 \}  的一个排列且逆序对数为奇数,当且仅当这个序列属于 {{1,3,2},{2,1,3},{3,2,1}}\{\{1,3,2\}, \{2, 1, 3\}, \{3, 2, 1\}\}

遍历整个序列,若当前元素为 1(或 0),则可以是:

  • 单独的一个 {1}\{1\}
  • 和前面的 {2}\{2\} 组合成 {2,1}\{2, 1\}
  • 和前面的 {3,2}\{3,2\} 组合成 {3,2,1}\{3, 2, 1\}

若当前元素为 2 或 3 时同理.

依照这个设计状态:dp[i][a][b][c][d][e][f],用于表示考虑到第 ii 个元素时:

  • 前面剩余单独的 {1}\{1\}a
  • 前面剩余单独的 {2}\{2\}b
  • 前面剩余单独的 {3}\{3\}c
  • 前面剩余的 {2,1}\{2, 1\}d
  • 前面剩余的 {1,3}\{1, 3\}e
  • 前面剩余的 {3,2}\{3, 2\}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],增加了一个单独的 {1}\{1\}
  • dp[i-1][a][b][c+1][d][e][f-1] * (c+1),花费任意一个 {3}\{3\}(由于有 c+1c+1{3}\{3\},所以有 c+1c+1 种选法),增加了一个 {3,2}\{3, 2\}
  • dp[i-1][a][b][c][d][e+1][f] * (e+1),花费一个 {13}\{1,3\}(同理,有 e+1e+1 种选法),组成了一个 {1,3,2}\{1,3,2\},这个组合合法,消去.

若当前元素为 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 & 1pre = (i-1) & 1,利用 dp[cur][...] 来代替 dp[i][...]dp[pre][...] 来代替 dp[i-1][...]

由于算出来的是划分数,而题目要求的是排列,所以还要乘 n!n!

#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;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D1-G
https://blog.jerrylab.top/posts/problem/anji2026/D1/G/
作者
Jerry
发布于
2026-08-01
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0