惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
A
About on SuperTechFans
J
Java Code Geeks
量子位
博客园 - 三生石上(FineUI控件)
博客园 - Franky
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
IT之家
IT之家
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

Shiroha白羽的博客

Golang 踩坑 —— interface 为参数的时候传 nil 指针 Codeforces Round 925 (Div. 3) Codeforces Round 924 (Div. 2) Codeforces Round 923 (Div. 3) Codeforces Round 922 (Div. 2) Codeforces Round 921 (Div. 2) Educational Codeforces Round 161 (Rated for Div. 2) Codeforces Round 920 (Div. 3) Codeforces Round 919 (Div. 2) Hello 2024 Good Bye 2023 Codeforces Round 918 (Div. 4) 个人备份的常用 macOS 清理命令 Codeforces Round 917 (Div. 2) Pinely Round 3 (Div. 1 + Div. 2) Educational Codeforces Round 160 (Rated for Div. 2) Codeforces Round 915 (Div. 2) Codeforces Round 914 (Div. 2) Codeforces Round 913 (Div. 3) Educational Codeforces Round 159 (Rated for Div. 2) Codeforces Round 912 (Div. 2) Codeforces Round 911 (Div. 2) CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!) Educational Codeforces Round 158 (Rated for Div. 2) Codeforces Round 910 (Div. 2) Codeforces Round 909 (Div. 3) Codeforces Round 908 (Div. 2) Educational Codeforces Round 157 (Rated for Div. 2) C++自定义的字面量 Codeforces Round 907 (Div. 2)
【2019多校第一场补题 / HDU6578】2019多校第一场A题1001Blan...
Shiroha · 2019-07-23 · via Shiroha白羽的博客

HDU6578链接

题意

有一串字符串,仅由 ${0, 1, 2, 3}$ 组成,长度为 $n$,同时满足 $m$ 个条件。每个条件由三个整数组成:$l、r、x$ 表示在这个字符串的 $[l, r]$ 这个区间内,有且仅有 $x$ 个不同的字符,求问可能的组合有多少种(mod 998244353)

分析题意

因为前几天刚刚写了牛客暑期多校第二场,其中有一道题:ABBA(我的题解)感觉有点接近,所以第一想法就是dp了。但是这道题的字符多,不能像ABBA一样压缩至一维。所以只能想想看当时牛客官方给的题解的方法了。
考虑到之后需要判断条件是否满足,所以我第一感觉就是得定义一个超多维度的dp数组:

1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][MAXN][MAXN];

各个维度的定义如下:

对于 dp[a][b][c][d][t]
表示整个字符串长度为 t ,最后一次出现 0 的位置为 a,最后一次出现 1 的位置为 b ,最后一次出现 2 的位置为 c ,最后一次出现 3 的位置为d

然后可以得到状态转移方程

1
2
3
4
dp[t + 1][b][c][d][t + 1] += dp[a][b][c][d][t];
dp[a][t + 1][c][d][t + 1] += dp[a][b][c][d][t];
dp[a][b][t + 1][d][t + 1] += dp[a][b][c][d][t];
dp[a][b][c][t + 1][t + 1] += dp[a][b][c][d][t];

当然这个数组肯定是没法开的,所以把 t 压缩了,变成滚动dp

1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][MAXN][2];

然后通过滚动的方式来实现。
但是这并不是卡死这种方法的原因。
根据上面这些,可以写出整个dp过程,大概就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (int t = 1; t <= n; t++) {
for (int a = 0; a <= t; a++)
for (int b = 0; b <= t; b++)
for (int c = 0; c <= t; c++)
for (int d = 0; d <= t; d++)
dp[a][b][c][d][t & 1] = 0;
for (int a = 0; a <= t; a++)
for (int b = 0; b <= t; b++)
for (int c = 0; c <= t; c++)
for (int d = 0; d <= t; d++) {
dp[t + 1][b][c][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][t + 1][c][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][b][t + 1][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][b][c][t + 1][t & 1] += dp[a][b][c][d][t ^ 1];
}
}

这个复杂的……这还没加上判断是否满足条件……
无论是时间是还是空间上,估计都悬(时间上应该是一定过不了了)
然后只能继续压缩。
考虑到最后无论哪种状态下,$a, b, c, d$ 四个变量中,必定有一个且仅有一个变量的值为 t 。而且在这道题中,字符 ${0, 1, 2, 3}$ 完全等价。所以我们再压缩一维

1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][2];

此时的意义如下:

对于变量 dp[x][y][z]
表示字符 ${0, 1, 2, 3}$ 中,其中一个最后出现的位置为当前的字符最后(由于这个条件恒成立,所以并未被记录在数组中),剩下的三个字符分别出现在$x, y, z$处,且保证 $i < x \leq y \leq z (i 为当前字符长度)$ (仅当 $x = y = 0$ 时满足前面一个等于号,后面的等于号同理。而字符串长度至少为1,且此时$x、y、z$均为0,所以不存在 $i = x$ 的情况。)而后面的长度为 2 的维度指代当前状态和 上一个状态(滚动dp)

可以得到状态转移方程:

1
2
3
4
5
// i 为当前字符串长度,cur 为当前状态,last 为上一个状态
dp[x][y][z][cur] += dp[x][y][z][last]; // 加入的字符与上一个加入的字符相同
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

得到整个 dp 过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dp[0][0][0][0] = 1;
int cur = 1;
int last = 0;
for (int i = 1; i <= n; i++)
for (int x = 0; x <= i; x++)
for (int y = 0; y <= x; y++)
for (int z = 0; z <= y; z++)
dp[x][y][z][cur] = 0;
for (int x = 0; x < i; x++)
for (int y = 0; y <= x; y++)
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] += dp[x][y][z][last];
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

dp[x][y][z][cur] %= mod; // 别忘了 mod
dp[i - 1][y][z][cur] %= mod;
dp[i - 1][x][z][cur] %= mod;
dp[i - 1][x][y][cur] %= mod;
}
swap(cur, last);

考虑条件
需要判断一个区间内是否满足有多个不同的字符
我们可以根据区间右端为基准,当前dp的字符串长度到达一个条件的右端的时候,通过 $x、y、z$ 的值来判断是否到达了要求,如果没有则将此 dp 的赋值为0。如果懒得思考可以直接分类讨论一下就行了。虽然代码会比较长。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 110;
const int mod = 998244353;

long long dp[MAXN][MAXN][MAXN][2];
// dp[i][j][k] 表示上一次出现不同数字的位置分别是 i、j、k、当前位置

struct Conditions {
int l, x;

Conditions(int ll, int xx) : l(ll), x(xx) {}
};

vector <Conditions> conditions[MAXN]; // 用来保存要求

int main() {
#ifdef ACM_LOCAL
freopen("./in.txt", "r", stdin);
freopen("./out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
int a, b, c;
for (int i = 0; i < MAXN; i++) {
conditions[i].clear();
}
for (int i = 0; i < m; i++) {
cin >> a >> b >> c;
conditions[b].push_back(Conditions(a, c)); // 要求按照 r 的不同来保存
}
dp[0][0][0][0] = 1;
int cur = 1;
int last = 0;
for (int i = 1; i <= n; i++) {
for (int x = 0; x <= i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] = 0;
}
}
}
for (int x = 0; x < i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] += dp[x][y][z][last];
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

dp[x][y][z][cur] %= mod;
dp[i - 1][y][z][cur] %= mod;
dp[i - 1][x][z][cur] %= mod;
dp[i - 1][x][y][cur] %= mod;
}
}
}
for (int s = 0; s < conditions[i].size(); s++) {
for (int x = 0; x < i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
int cnt = 1 + (x >= conditions[i][s].l ? 1 : 0) + (y >= conditions[i][s].l ? 1 : 0) +
(z >= conditions[i][s].l ? 1 : 0); // 判断剩下三个位置是否满足条件
if (cnt != conditions[i][s].x)
dp[x][y][z][cur] = 0;
}
}
}
}
swap(cur, last);
}
long long ans = 0; // 求算最终答案。需要把所有的情况都加起来
for (int x = 0; x < n; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
ans += dp[x][y][z][last];
ans %= mod;
}
}
}
cout << ans << endl;
}
return 0;
}