按指令顺序回溯分配服务器,并始终按编号从小到大尝试,第一次找到的完整方案就是字典序最小解。
OJ: luogu
题目 ID: P8838
难度:普及-
标签:dfs枚举构造
日期: 2026-06-19 09:04
题意
有 n 个服务器,第 i 个服务器最多能处理大小为 a[i] 的数据。
接下来按顺序有 k 条指令,第 i 条指令需要处理大小为 b[i] 的数据。每条指令都必须分配给一个还没被用过的空闲服务器,而且这个服务器的容量要不小于 b[i]。
要求输出一个分配序列 p[1..k],表示第 i 条指令分给服务器 p[i],并且这个序列要字典序最小。如果根本无法完成分配,就输出 -1。
思路
题目规模非常小,n, k <= 6,所以最直接的办法就是回溯搜索。
这个最朴素、最容易理解的版本如下:
cpp
// brute.cpp:小数据直接回溯所有分配方案,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10;
int n, k;
int a[MAXN], b[MAXN];
int used[MAXN];
int ans[MAXN];
bool found_answer;
void dfs(int step) {
if (found_answer) {
return;
}
if (step > k) {
found_answer = true;
return;
}
for (int i = 1; i <= n; i++) {
if (used[i]) {
continue;
}
if (a[i] < b[step]) {
continue;
}
used[i] = 1;
ans[step] = i;
dfs(step + 1);
if (found_answer) {
return;
}
used[i] = 0;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= k; i++) {
cin >> b[i];
}
memset(used, 0, sizeof(used));
found_answer = false;
dfs(1);
if (!found_answer) {
cout << -1 << '\n';
return 0;
}
for (int i = 1; i <= k; i++) {
if (i > 1) {
cout << ' ';
}
cout << ans[i];
}
cout << '\n';
return 0;
}为什么第一个搜到的解就是字典序最小
我们按指令顺序递归。
递归到第 step 条指令时,枚举它可以分配给哪些服务器,并且服务器编号从小到大尝试。
这样搜索树的遍历顺序就是:
- 先尽量让
p[1]小; - 在
p[1]固定后,再尽量让p[2]小; - 依次类推。
所以第一次找到的完整合法方案,天然就是字典序最小的方案。
搜索怎么写
设 used[i] 表示服务器 i 是否已经被占用。
当前处理第 step 条指令时:
- 枚举服务器
1..n; - 如果这个服务器还空闲,且
a[i] >= b[step],就可以尝试分配; - 标记已使用,递归下一条指令;
- 如果后面能成功,就直接结束;
- 否则回溯撤销标记。
如果所有可能都试完还不成功,说明无解。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10;
int n, k;
int a[MAXN], b[MAXN];
int used[MAXN]; // 服务器是否已经被占用
int ans[MAXN]; // 当前构造的分配方案
bool found_answer;
void dfs(int step) {
if (found_answer) {
return;
}
if (step > k) {
found_answer = true;
return;
}
for (int i = 1; i <= n; i++) {
if (used[i]) {
continue;
}
if (a[i] < b[step]) {
continue;
}
used[i] = 1;
ans[step] = i;
dfs(step + 1);
if (found_answer) {
return;
}
used[i] = 0;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= k; i++) {
cin >> b[i];
}
memset(used, 0, sizeof(used));
found_answer = false;
dfs(1);
if (!found_answer) {
cout << -1 << '\n';
return 0;
}
for (int i = 1; i <= k; i++) {
if (i > 1) {
cout << ' ';
}
cout << ans[i];
}
cout << '\n';
return 0;
}复杂度
- 时间复杂度:最坏
- 空间复杂度:
但这里 n <= 6,这个复杂度完全足够。
总结
这题的关键不是优化,而是看出“字典序最小”正好可以通过搜索顺序直接保证。
只要按服务器编号从小到大回溯,找到的第一个完整解就是答案。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
