题解:洛谷 P3005 The Trough Game
【题目来源】
洛谷:P3005 [USACO10DEC] The Trough Game S
【题目描述】
Farmer John and Bessie are playing games again. This one has to do with troughs of water.
Farmer John has hidden N (1 <= N <= 20) troughs behind the barn, and has filled some of them with food. Bessie has asked M (1 <= M <= 100) questions of the form, 'How many troughs from this list (which she recites) are filled?'.
Bessie needs your help to deduce which troughs are actually filled.
Consider an example with four troughs where Bessie has asked these questions (and received the indicated answers):
-
'How many of these troughs are filled: trough 1' --> 1 trough is filled
-
'How many of these troughs are filled: troughs 2 and 3' --> 1 trough is filled
-
'How many of these troughs are filled: troughs 1 and 4' --> 1 trough is filled
-
'How many of these troughs are filled: troughs 3 and 4' --> 1 trough is filled
From question 1, we know trough 1 is filled.
From question 3, we then know trough 4 is empty.
From question 4, we then know that trough 3 is filled.
From question 2, we then know that trough 2 is empty.
Farmer John 和 Bessie 在玩一个游戏。
Farmer John 准备了 \(n\) 个槽(\(1\le n\le20\)),其中一些槽中藏有食物。Bessie 为了知道哪些槽中有食物,会询问 \(m\) 个形如“第 \(x_1\cdots x_k\) 号槽中是否有食物?”的问题(\(1\le m\le100,1\le k\le n\))。
请你帮忙求出哪几个槽中有食物。
【输入】
* Line 1: Two space-separated integers: N and M
* Lines 2..M+1: A subset of troughs, specified as a sequence of contiguous N 0's and 1's, followed by a single integer that is the number of troughs in the specified subset that are filled.
第一行包含两个整数 \(n,m\),分别表示槽的个数和 Bessie 询问的问题数。
接下来 \(m\) 行每行包含一个长度为 \(n\) 的 \(01\) 序列和一个整数 \(t\),其中 \(01\) 序列中的 \(1\) 表示询问中提到了这个位置的槽,\(t\) 表示这些槽中有 \(t\) 份食物。
【输出】
* Line 1: A single line with:
* The string 'IMPOSSIBLE' if there is no possible set of filled troughs compatible with Farmer John's answers.
* The string 'NOT UNIQUE' if Bessie cannot determine from the given data exactly what troughs are filled.
* Otherwise, a sequence of contiguous N 0's and 1's specifying which troughs are filled.
输出共一行。
若无解,则输出 IMPOSSIBLE。
若不止一个解,则输出 NOT UNIQUE。
若有唯一解,则输出一个 \(01\) 序列,其中 \(1\) 表示这个位置的槽中有食物。
【输入样例】
4 4
1000 1
0110 1
1001 1
0011 1
【输出样例】
1010
【核心思想】
-
问题分析:有 \(N\) 个槽(\(N \le 20\)),每个槽可能有食物(1)或没有(0)。Bessie 提出了 \(M\) 个询问,每个询问用一个长度为 \(N\) 的 01 串表示她问的是哪些槽,并给出这些槽中有食物槽的数量 \(t\)。要求根据这些信息推断出唯一的填充状态,或者判断无解/多解。这是一个状态空间搜索 + 条件匹配问题,由于 \(N\) 很小,可以直接枚举所有 \(2^N\) 种状态,检查是否满足所有询问。
-
算法选择:
- 枚举所有状态:用整数 \(0 \sim 2^N-1\) 表示所有可能的填充状态(二进制位 \(1\) 表示有食物)。
- 位运算加速:将每个询问的 01 串转换为位掩码
mask。对于每个状态state,计算state & mask的二进制中 1 的个数(即该询问覆盖的槽中有食物的数量),与题目给出的 \(t\) 比较。 - 收集合法状态:遍历所有状态,记录所有满足所有询问的状态。根据合法状态数量输出结果:0 个输出
IMPOSSIBLE,1 个输出对应的 01 串,多个输出NOT UNIQUE。
-
关键步骤:
- 读入与预处理:读取 \(N, M\),将每个询问的 01 字符串转为整数掩码
mask[i],存储对应的答案need[i]。 - 枚举状态:
for (int state = 0; state < (1 << N); state++):- 对每个询问 \(i\),计算
popcount(state & mask[i]),若与need[i]不等则跳过该状态。 - 若所有询问通过,则将该状态加入合法列表。
- 对每个询问 \(i\),计算
- 输出结果:
- 若合法列表为空:输出
IMPOSSIBLE。 - 若合法列表大小 > 1:输出
NOT UNIQUE。 - 否则将唯一的状态转为 \(N\) 位 01 串并输出(注意补前导零)。
- 若合法列表为空:输出
- 读入与预处理:读取 \(N, M\),将每个询问的 01 字符串转为整数掩码
-
时间/空间复杂度:
- 时间复杂度:\(O(2^N \times M)\),\(N \le 20\),\(2^{20} \approx 10^6\),\(M \le 100\),总运算约 \(10^8\) 次,可接受(位运算优化后实际很快)。
- 空间复杂度:\(O(M)\)。
-
枚举 + 位运算的核心思想:
- 状态空间有限:\(N \le 20\) 使得枚举所有可能状态成为可行策略,这是本题的关键约束。
- 位运算高效性:使用整数位掩码表示子集,
&运算求交集,__builtin_popcount快速统计 1 的个数,避免了字符串或数组的逐位比较。 - 暴力验证:对每个状态直接验证所有约束,简单可靠,无需复杂推理。
- 适用场景:当搜索空间不大(如 \(2^{20}\) 以内)且约束条件为线性子集计数时,枚举 + 位运算是最直接且高效的方法。
【算法标签】
普及 #位运算
【代码详解】
```cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 105; // 最大问题数
int n, M, cnt; // n: 槽的数量, M: 问题数量, cnt: 临时存储每个问题的答案
string s; // 临时存储输入的01序列
int mask[N], need[N]; // mask[i]: 第i个问题的01位掩码, need[i]: 该问题期望的填充槽数量
vector<int> solution; // 存储所有满足条件的方案(状态压缩表示)
// 将二进制字符串转换为整数(位掩码)
int BtoD(string s)
{
int ans = 0;
for (int i = 0; i < s.size(); i++)
{
ans = ans * 2 + s[i] - '0';
}
return ans;
}
// 十进制转任意进制(这里转二进制),返回字符串
string Dtom(int n, int m)
{
string d = "0123456789ABCDEF", ans = "";
while (n > 0)
{
ans = d[n % m] + ans;
n /= m;
}
return ans;
}
int main()
{
cin >> n >> M; // 输入槽个数和问题数
// 读入每个问题
for (int i = 0; i < M; i++)
{
cin >> s >> cnt; // 输入01序列和答案
int m = BtoD(s); // 将01序列转为掩码
mask[i] = m;
need[i] = cnt;
}
// 枚举所有可能的填充状态(0 ~ 2^n - 1)
for (int state = 0; state < (1 << n); state++)
{
bool ok = true;
// 检查当前状态是否满足所有问题的答案
for (int i = 0; i < M; i++)
{
int cnt = __builtin_popcount(state & mask[i]); // 计算当前状态与问题掩码的交集中1的个数
if (cnt != need[i])
{
ok = false;
break;
}
}
if (ok)
{
solution.push_back(state); // 记录满足条件的方案
}
}
// 根据方案数量输出结果
if (solution.empty())
{
cout << "IMPOSSIBLE" << endl;
}
else if (solution.size() > 1)
{
cout << "NOT UNIQUE" << endl;
}
else
{
// 注意:当方案为0时,Dtom返回空字符串,但题目要求输出n位01序列
// 此处沿用原代码逻辑,未作处理,但实际应补前导零
cout << Dtom(solution[0], 2);
}
return 0;
}
【运行结果】
4 4
1000 1
0110 1
1001 1
0011 1
1010
浙公网安备 33010602011771号