0%

另外,再提醒你两个后续也会出问题的点,虽然它们不是这次输出变成 1..8 的主因:

  • 你的 set<L_R_A_B_C> memo 判重会用到 operator<,但你把 step 也放进比较里了。
1
2
3
4
5
bool operator<(const L_R_A_B_C &o) const {
if (step != o.step) return step > o.step;
if (hands != o.hands) return hands < o.hands;
return tower_ls < o.tower_ls;
}

这会导致“同一个局面但 step 不同”被当成两个不同状态,不是你真正想要的“局面判重”。

  • 你的边权其实是 0/1 的:
    • > 手,代价是 1
    • > 栈,代价是 0
      这种情况最好用 0-1 BFS,或者至少用 dist 做最短路,不能简单地“第一次入队就判重”。

如果你愿意,我下一条可以直接按你这份代码的风格,给你整理一个最适合这题打表的状态设计 + 0-1 BFS 写法

最直接的原因是:你把初始栈建反了

你后面的所有操作都把 vector::back() 当成栈顶:

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
vector<L_R_A_B_C> op_lrabc() const {
vector<L_R_A_B_C> res;
for (int i = 0; i < 2; ++i) {
if (hands[i] == 0) continue;
for (int j = 0; j < 3; ++j) {
if (tower_ls[j].empty() || tower_ls[j].back() > hands[i]) {
L_R_A_B_C to = *this;
to.tower_ls[j].push_back(hands[i]);
to.hands[i] = 0;
res.push_back(to);
}
}
}
for (int i = 0; i < 2; ++i) {
if (hands[i] != 0) continue;
for (int j = 0; j < 3; ++j) {
if (tower_ls[j].empty()) continue;
L_R_A_B_C to = *this;
to.hands[i] = tower_ls[j].back();
to.tower_ls[j].pop_back();
to.step++;
res.push_back(to);
}
}
return res;
}

但你初始化的时候写的是:

1
2
3
4
5
6
7
8
9
ll bfs(ll n) {
L_R_A_B_C st;
for (int i = 1; i <= n; ++i) {
st.tower_ls[0].push_back(i);
}
set<L_R_A_B_C> memo;
priority_queue<L_R_A_B_C> q;
q.push(st);
memo.insert(st);

这样 A 里实际是 [1,2,3,...,n],而 back()n

也就是说,你程序理解出来的“栈顶”是最大的盘,这和题意“越靠近栈底越大”正好反了。

所以你的程序会认为可以这样合法移动:

  1. 先拿出 n 放到空的 C

  2. 再拿出 n-1 放到 C,因为此时 C.back() = n > n-1

  3. 再拿出 n-2

  4. 一直这样下去

于是每个盘都只需要被拿起一次,答案自然就成了 n,所以你才会打出:

  • 1:1

  • 2:2

  • 3:3

  • 8:8

你这里概念上应该让初始 A 满足:如果 back() 是栈顶,那么 back() 必须是最小盘,也就是初始内容应当是 {n,n-1,...,1},而不是 {1,2,...,n}

题目大意

题目大意

给定一个只包含字符 01 的字符串 SS

我们称一个序列为“茄子序列”,当且仅当该序列中逆序对的数量为奇数(在 01 序列中,逆序对数量即为子序列 10 的数量,例如 10100 有 5 个逆序对)。

对于一个序列 SS',定义 f(S)f(S') 为:将 SS' 连续划分成 kk 个子段,使得每个子段都是“茄子序列”的最小划分段数 kk。如果无论怎么划分都无法满足要求,则 f(S)=0f(S') = 0

求原字符串 SS 的所有非空子序列(共 2S12^{|S|}-1 个)的 ff 值之和。答案对 998244353998244353 取模。

数据范围

  • 测试数据组数 T5T \le 5

  • 所有给定的字符串 SS 的总长度 S107\sum |S| \le 10^7

样例数据

1
2
3
4
5
6
7
8
9
10
输入:
3
1100
10110010
01101001100101101001011001101001

输出:
4
160
981596155

样例解释

以第一组样例 1100 为例:
该字符串共有 241=152^4 - 1 = 15 个非空子序列。

  • 长度为 1 的子序列(两个 1,两个 0):逆序对均为 0(偶数),无法合法划分,ff 值为 0。

  • 长度为 2 的子序列:

    • 1100:逆序对为 0,无法合法划分,ff 值为 0。
    • 10(共有 4 个,分别由原串位置(1,3), (1,4), (2,3), (2,4)组成):逆序对数为 1(奇数),整个序列本身就是一个“茄子序列”,最少可划分为 1 段,因此 f(10)=1f(10) = 1。这部分 ff 值之和为 4×1=44 \times 1 = 4
  • 长度为 3 的子序列(两个 110,两个 100):逆序对数均为 2(偶数),且无论如何划分,都无法切分成若干个逆序对均为奇数的子段,因此 ff 值为 0。

  • 长度为 4 的子序列(1100):逆序对数为 4(偶数),同样无法合法划分,ff 值为 0。

综上所述,所有非空子序列的 ff 值之和为 4。

思路讲解

PDF

我们先看段,再看串,最后看如何前缀 dp。(注意,所谓的串,是假设我们已经从原字符串取出来了一个子序列,我们称其为,而是对这个串进行分段以后的这个段。)

image

上面其实讲的事情很简单,就是说一个段,可以用 1 的数量和逆序对数量来表征。

image

那么,怎么样求一个串的最优分段数量呢?

image

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct State {
// d[num1][rev] 表示当前已经读完某个前缀后,
// num1 就是1的个数的奇偶性,rev 就是 逆序对 数量
// 在所有“最后一个未结束子段的内部状态为 $(x,y)$”的切分方案中,前面已经结束的合法子段数的最小值。
// num1,rev 代表未结束子串状态,未结束子串允许是空串
array<array<ll, 2>, 2> d;

auto operator<=>(const State &) const = default;

[[nodiscard]] State next_state(ll op) const {
array<array<ll, 2>, 2> nd{INF, INF, INF, INF};
for (int num1 = 0; num1 < 2; ++num1) {
for (int rev = 0; rev < 2; ++rev) {
ll to_num1 = num1 ^ op, to_rev = rev ^ (op == 0 ? num1 : 0);
nd[to_num1][to_rev] = min(nd[to_num1][to_rev], d[num1][rev]);
if (rev == 1) {
nd[op][0] = min(nd[op][0], d[num1][rev] == INF ? INF : d[num1][rev] + 1);
}
}
}
return State{nd};
}
};

AC代码

AC
https://acm.hdu.edu.cn/contest/view-code?cid=1199&rid=18676

心路历程(WA,TLE,MLE……)

注意,不要在循环内新建这个变量,可以把这个变量放在外面(说白了就是建内存块,delete 析构内存慢)

注意在构造 nxt 数组的时候,一定不要忘记给已经到过的点也要建边

注意,防止哨兵值参与计算,如果是 INF 哨兵值,就直接 continue

PDF