问题描述
Tingua\texttt{Tingua}Tingua 社交俱乐部要为新舞厅铺设木地板。他们收到了一批捐赠的木板,这些木板宽度相同(LLL 厘米),但长度不同。舞厅是一个 N×MN \times MN×M 米的长方形区域。铺设木板时需要满足以下限制:
- 方向一致:所有木板必须平行放置,要么全部横向(长度方向平行于 NNN 边),要么全部纵向(长度方向平行于 MMM 边)。
- 无重叠:木板必须并排铺设,不能重叠,完全覆盖整个地板。
- 拼接限制:每行(横向时)或每列(纵向时)最多只能用两块木板拼接完成。也就是说,如果一块木板长度不够,最多只能与另一块木板拼接。
- 不切割:不能锯断任何木板。
目标:判断是否能用捐赠的木板覆盖整个舞厅地板,如果可能,求出所需的最少木板数量。
输入格式
- 每个测试用例包含:
- 两个整数 NNN 和 MMM (1≤N,M≤104)(1 \leq N,M \leq 10^4)(1≤N,M≤104),表示舞厅尺寸(米)。
- 一个整数 LLL (1≤L≤100)(1 \leq L \leq 100)(1≤L≤100),表示木板宽度(厘米)。
- 一个整数 KKK (1≤K≤105)(1 \leq K \leq 10^5)(1≤K≤105),表示木板数量。
- KKK 个整数 XiX_iXi (1≤Xi≤104)(1 \leq X_i \leq 10^4)(1≤Xi≤104),表示每块木板的长度(米)。
- 输入以
0 0结束。
输出格式
对于每个测试用例,输出一行:
- 如果可能覆盖,输出最少木板数量。
- 如果不可能,输出
impossivel(葡萄牙语的“不可能”)。
解题思路
核心问题分解
这个问题实际上可以分解为两个独立的子问题,分别对应两种木板放置方向:
方向1:木板横向放置
- 木板长度方向平行于 NNN 边。
- 需要覆盖 MMM 米宽的边,木板宽度为 L/100L/100L/100 米。
- 如果 MMM 不能被 L/100L/100L/100 整除,这个方向直接不可能。
- 需要的“行数”:numLines=M/(L/100)numLines = M / (L/100)numLines=M/(L/100)。
- 每一行的长度必须是 NNN 米。
- 每一行只能用 111 块或 222 块木板:
- 111 块:木板长度正好为 NNN 米。
- 222 块:两块木板长度之和为 NNN 米。
方向2:木板纵向放置
- 木板长度方向平行于 MMM 边。
- 需要覆盖 NNN 米宽的边。
- 需要满足的条件与方向1类似,只是 NNN 和 MMM 角色互换。
算法设计
1. 方向检查函数
我们需要一个函数 tryDirection(roomLength, roomWidth, L, planks):
roomLength:木板需要覆盖的长度(米)。roomWidth:需要铺设的行/列总宽度(米)。- 返回:覆盖所需的最少木板数,如果不可能返回 −1-1−1。
2. 避免浮点精度问题
由于 LLL 是厘米,而房间尺寸是米,直接使用浮点数计算 L/100L/100L/100 可能导致精度问题。更好的方法是将所有单位统一转换为厘米:
- 房间长度:roomLength×100roomLength \times 100roomLength×100 厘米
- 房间宽度:roomWidth×100roomWidth \times 100roomWidth×100 厘米
- 木板宽度:LLL 厘米
- 木板长度:Xi×100X_i \times 100Xi×100 厘米
这样所有计算都在整数范围内进行,避免了浮点误差。
3. 木板分配策略(贪心算法)
对于每一行/列,我们按以下优先级分配木板:
- 优先使用单块完整木板:如果有长度正好等于目标长度的木板,直接使用。
- 使用两块木板拼接:如果没有完整长度的木板,寻找一对木板,它们的长度之和等于目标长度。
为什么贪心策略有效?
- 使用单块木板总是最优的(节省一块木板)。
- 当需要拼接时,任意一对长度之和等于目标长度的木板都是等价的。
- 我们只需要关心木板数量,不关心具体哪两块木板拼接。
4. 实现细节
- 使用
map<int, int>统计每种长度(厘米)的木板数量。 - 对于每一行/列,尝试分配木板:
- 检查是否有完整长度的木板。
- 如果没有,遍历所有可能的木板长度 aaa,寻找 b=target−ab = target - ab=target−a。
- 注意处理 a=ba = ba=b 的情况(需要两块相同长度的木板)。
- 如果某一无法分配木板,该方向不可能。
5. 复杂度分析
- 木板数量 K≤105K \leq 10^5K≤105。
- 对于每个方向,最坏情况下需要遍历所有木板寻找拼接对。
- 使用
map查找和更新是 O(logK)O(\log K)O(logK)。 - 总体复杂度:O(KlogK)O(K \log K)O(KlogK),可以接受。
边界情况考虑
- 木板宽度不能整除房间宽度:直接不可能。
- 木板长度不足:即使拼接也无法达到目标长度。
- 木板数量不足:即使每行/列都能分配,但总行/列数需要的木板数超过 KKK。
- 大整数运算:全部转换为厘米后,最大值为 104×100=10610^4 \times 100 = 10^6104×100=106,在
int范围内。
参考代码
// The Club Ballroom
// UVa ID: 11493
// Verdict: Accepted
// Submission Date: 2025-12-01
// UVa Run Time: 0.060s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 尝试覆盖一个方向,返回最少木板数,如果不可能返回-1
int tryDirection(int roomLength, int roomWidth, int L, vector<int>& planks) {
// 全部转换为厘米
int lengthCm = roomLength * 100;
int widthCm = roomWidth * 100;
// 检查木板宽度是否能整除房间宽度
if (widthCm % L != 0) return -1;
int numLines = widthCm / L;
// 统计木板数量(厘米)
map<int, int> count;
for (int len : planks) {
count[len * 100]++;
}
map<int, int> tempCount = count;
int totalUsed = 0;
for (int i = 0; i < numLines; i++) {
// 优先使用单块完整木板
if (tempCount[lengthCm] > 0) {
tempCount[lengthCm]--;
totalUsed++;
continue;
}
// 尝试用两块木板拼接
bool found = false;
for (auto& p : tempCount) {
int a = p.first;
if (a >= lengthCm) continue;
int b = lengthCm - a;
if (b <= 0) continue;
if (a == b) {
if (p.second >= 2) {
tempCount[a] -= 2;
if (tempCount[a] == 0) tempCount.erase(a);
totalUsed += 2;
found = true;
break;
}
} else {
if (tempCount.find(b) != tempCount.end() && tempCount[b] > 0) {
tempCount[a]--;
tempCount[b]--;
if (tempCount[a] == 0) tempCount.erase(a);
if (tempCount[b] == 0) tempCount.erase(b);
totalUsed += 2;
found = true;
break;
}
}
}
if (!found) return -1;
}
return totalUsed;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M;
while (cin >> N >> M, N || M) {
int L, K;
cin >> L >> K;
vector<int> planks(K);
for (int i = 0; i < K; i++) cin >> planks[i];
int ans = INT_MAX;
// 方向1:木板横向放置
int result1 = tryDirection(N, M, L, planks);
if (result1 != -1) ans = min(ans, result1);
// 方向2:木板纵向放置
int result2 = tryDirection(M, N, L, planks);
if (result2 != -1) ans = min(ans, result2);
if (ans == INT_MAX) cout << "impossivel\n";
else cout << ans << "\n";
}
return 0;
}
代码说明
- 单位转换:将所有尺寸统一转换为厘米,避免浮点精度问题。
- 木板统计:使用
map<int, int>统计每种长度的木板数量,便于查找和更新。 - 贪心分配:对于每一行/列,优先使用完整木板,其次寻找拼接对。
- 双方向检查:分别检查横向和纵向放置的可能性,取最小值。
- 输入优化:使用
ios::sync_with_stdio(false)和cin.tie(nullptr)加快输入速度。
总结
本题的关键在于理解木板铺设的限制条件,特别是方向一致和最多两块拼接的限制。通过将问题分解为两个独立的方向,并对每个方向使用贪心策略分配木板,可以高效求解。注意单位转换避免浮点精度问题,以及合理选择数据结构提高效率。

177

被折叠的 条评论
为什么被折叠?



