AT_abc443_f Non-Increasing Number 题解

属于是看上去是模板,实际上有点小巧思的题目。

考虑我们如何比较两个方案大小。可以先比位数然后比字典序。因此我们只需要先求出最小位数然后再构造最小字典序方案即可。

和数位以及倍数相关容易想到同余最短路(和这题很像)。我们拆成 个点,记作 ,表示用若干个数拼成摸 ,最后一位数为 ,到这个点的最短路就是拼成摸 ,最后一位数为 的最小位数。

考虑每次往后拼一位。对于每个 ,连边 ,权值为 。此时从 跑多源多汇的最短路即可求出距离了。

接下来是构造方案。容易想到建出最短路径树然后直接贪心选择即可。

到这里其实没啥思维难度。

接下来是实现部分。首先暴力连边是 条边,不超时间空间也会超。可以考虑前缀和优化建图,对于每个 ,连边 ,权值为 ,此时就剩下 条边,然后跑双端队列 BFS 可以做到线性,但是在 的时间内也比较难以通过。此时大力卡常可能可以通过,不过我没试过。

但是题解有一个非常巧妙的不用显式建图的方法。显然最短路不只一条,我们希望找到最短的那条。我们将代价设置为 ,并且定义偏序关系为先比 然后比 ,但是有两个问题,首先比较时间比较耗时,其次存不下。

我们依旧考虑一次扩展,每次扩展都是往后加一个字符,根据字典序的比较方法,如果如果前面 个字符已经比出大小了,之后再加字符也不会改变大小,因此我们只需要一开始让所有方案按照字典序塞进队列里,在扩展的过程中相同长度的方案就自动按照字典序拍好了,我们第一次到达这个点时取答案,记录前驱即可还原整个答案。

实际上我们也不需要 个点,取第一次到达这个点的 值往后枚举即可。如果你使用拆点做法的话,需要注意 的边长度相等且之前没有访问时可以覆盖前驱(由于没有访问过,因此原本的方案肯定在后面,不会更优),其它边则不行。

参考代码:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <algorithm>
#include <cassert>
#include <iostream>
#include <queue>
#include <vector>

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;

namespace numbers {
template <typename T>
T inf() {
assert(false);
}

template <>
int inf<int>() {
return 0x3f3f3f3f;
}

template <>
uint inf<uint>() {
return 0x3f3f3f3f;
}

template <>
ll inf<ll>() {
return 0x3f3f3f3f3f3f3f3f;
}

template <>
ull inf<ull>() {
return 0x3f3f3f3f3f3f3f3f;
}
} // namespace numbers

namespace graph {
const auto index = [](uint mod, uint last_digit) {
return mod * 10 + last_digit;
};

constexpr const uint npos = static_cast<uint>(-1);

struct graph {
uint n;

std::vector<uint> dis;
std::vector<uint> prev_point;

void bfs() {
std::fill(dis.begin(), dis.end(), numbers::inf<uint>());
std::fill(prev_point.begin(), prev_point.end(), npos);
std::vector<bool> vis(n);

std::deque<uint> q;

uint real_n = n / 10;

for (size_t i = 1; i < 10; i++) {
q.push_back(index(i % real_n, i));
dis[index(i % real_n, i)] = 0;
}

while (!q.empty()) {
auto f = q.front();
q.pop_front();

if (vis[f]) {
continue;
}
vis[f] = true;

uint mod = f / 10, digit = f % 10;

{
/**
* 填一个数
*/

uint nxt = (mod * 10 + digit) % real_n;
uint dest = index(nxt, digit);
if (!vis[dest] && dis[dest] > dis[f] + 1) {
dis[dest] = dis[f] + 1;
prev_point[dest] = f;
q.push_back(dest);
}

}
if (digit != 9) {
/**
* 换。
*/

uint nxt_digit = digit + 1;
uint dest = index(mod, nxt_digit);
if (!vis[dest] && dis[dest] >= dis[f]) {
/**
* 实际上和直接双端队列 bfs 记录前驱
* 相比只在这里多了一个 = 而已。
*/
dis[dest] = dis[f];
prev_point[dest] = f;
q.push_front(dest);
}
}
}

return;
}

graph(uint _n) : n(_n), dis(_n), prev_point(_n) {}
};
} // namespace graph

int main() {
uint n;
std::cin >> n;
graph::graph g(n * 10);
g.bfs();
auto min_digit = g.dis[graph::index(0, 9)];

if (min_digit == numbers::inf<uint>()) {
std::cout << -1 << std::endl;
return 0;
}

std::string s;
s.reserve(min_digit);

uint cur_pos = graph::index(0, 9);

for (uint j = 0; j < min_digit; j++) {
while (g.prev_point[cur_pos] / 10 == cur_pos / 10) {
cur_pos = g.prev_point[cur_pos];
}
assert(cur_pos % 10 == g.prev_point[cur_pos] % 10);
s.push_back((cur_pos % 10) + '0');
cur_pos = g.prev_point[cur_pos];
}
while (g.prev_point[cur_pos] != graph::npos) {
cur_pos = g.prev_point[cur_pos];
}
s.push_back((cur_pos % 10) + '0');
std::reverse(s.begin(), s.end());

std::cout << s << std::endl;
return 0;
}


AT_abc443_f Non-Increasing Number 题解
https://blogs.sving1024.top/posts/25185/
发布于
2026年2月1日
许可协议