一、 题目描述
点奶茶时可以选择不同的小料选项: “加冰/不加冰”,“加糖/不加糖”,“加珍珠/不加珍珠”,“加布丁/不加布丁”,等等。我们用零一串来表示顾客的偏好,如果用上面的四个选项,那么 string 1100 表示 “加冰、加糖、不要珍珠、不要布丁”。
小明要给他的N个朋友每人买杯奶茶。奶茶店给出的小料选择项有P项。N个朋友每人给出了其个人的偏好。但是小明觉得给每个人买不同的实在是太麻烦了,于是他打算只买一种奶茶。每当一个个偏好没有被满足时,他的朋友会抱怨一次。比如:两个朋友的偏好分别是 101 和 010,小明买了 001,那么第一个朋友会抱怨1次,第二个朋友会抱怨2次,总共有3次抱怨。
除此之外,还给出了M个零一串,表示奶茶店不提供的奶茶类型,小明不能买这些类型。比如,奶茶店可能不愿意提供“加冰”、“热饮”这类奶茶。
小明能得到的最小抱怨数是多少?
1. 输入
- T:表示共有T个用例来进行测试
- 对于每一个例子:
– 第一行:N、M、P
– 接下来有N行:每行一个string(由0/1组成),表示每个朋友的要求
– 接下来有M行:每行一个string(由0/1组成),表示奶茶店不能做的搭配
2. 输出
每个测试用例输出一行 Case #x: y
·x:第x个用例(从1开始)
·y:可以获得的最小的抱怨数
3. Limits
- 1 ≤ T ≤ 100.
- Time limit: 30 seconds per test case.
- Memory limit: 1 GB.
- All of the forbidden types of milk tea are different.
Small dataset (Test set 1 - Visible)
- 1 ≤ N ≤ 10.
- 1 ≤ M ≤ min(10, 2P - 1).
- 1 ≤ P ≤ 10.
Large dataset (Test set 2 - Hidden)
- 1 ≤ N ≤ 100.
- 1 ≤ M ≤ min(100, 2P - 1).
- 1 ≤ P ≤ 100.
二、 解题思路
如果没有M个被禁止掉的组合,我们只需要对P种选项的每一个做投票,选择多数人选择的那个就可以了。现在有M个被禁止的组合,如果我们手中有 (M+1) 种列出的组合,那也必定有最终能被实现的组合。
于是问题转化成求最优的前
(
M
+
1
)
(M+1)
(M+1) 种组合。下面证明:如果
c
h
o
i
c
e
[
0
,
1
,
.
.
.
,
p
]
choice[0, 1, ..., p]
choice[0,1,...,p] 在前 (M+1) 个之内,那么
c
h
o
i
c
e
[
0
,
1
,
.
.
.
,
p
−
1
]
choice[0, 1, ..., p-1]
choice[0,1,...,p−1] 一定也在前 (M+1) 个之内。
证明: 假设 choice[0, 1, ..., p] 是前 (M+1) 个,而 choice[0, 1, ..., p-1] 不是前 (M+1) 个;
设 choice[p] 增加的抱怨数为x;
那么对 choice[0, 1, ..., p-1] 以及排在其之前的 choice' [0, 1, ..., p-1] 都增加 choice[p];
必有 choice[0, 1, ..., p] 排在所有的 choice' [0, 1, ..., p] 之后;
由假设得,choice' 至少有 M+1 个;
那么 choice[0, 1, ..., p] 必不在前 (M+1) 个之内,
与假设矛盾,则假设不成立。
得到了上面的结论之后,我们就能够以 O ( 1 ) O(1) O(1) 的复杂度由 c h o i c e [ 0 , 1 , . . . , p − 1 ] choice[0, 1, ..., p-1] choice[0,1,...,p−1] 来得到 c h o i c e [ 0 , 1 , . . . , p ] choice[0, 1, ..., p] choice[0,1,...,p].
三、 代码描述
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
class CChoice{
public:
string c_str;
int c_cost;
bool operator <(const CChoice &other) const{
return c_cost < other.c_cost;
}
};
int solve(int N, int M, int P, vector<string> &Y, vector<int>agree){
class CChoice Choice;
vector<class CChoice> best;
// 初始化best
Choice.c_str = '0';
Choice.c_cost = agree[0];
best.push_back(Choice);
Choice.c_str = '1';
Choice.c_cost = N- agree[0];
best.push_back(Choice);
// 主体部分
for(int i = 1; i < P; i++){
vector<class CChoice> cur;
for(int j = 0; j < best.size(); j++){
Choice.c_str = best[j].c_str+'0';
Choice.c_cost = best[j].c_cost+agree[i];
cur.push_back(Choice);
Choice.c_str = best[j].c_str+'1';
Choice.c_cost = best[j].c_cost+N-agree[i];
cur.push_back(Choice);
}
sort(cur.begin(), cur.end());
//这里保留前2M个最优,主要是担心出现大量cost值即抱怨数相同的情况
best.assign(cur.begin(), min(cur.begin()+M+M, cur.end()));
}
for(int i = 0; i < best.size(); i++){
for(int j = 0; j < M; j++){
if(best[i].c_str == Y[j])
break;
if(j == (M-1))
return best[i].c_cost;
}
}
}
int main(){
int T, N, M, P, res;
cin >> T;
for(int i = 1; i <= T; i++){
// 数据输入
cin >> N >> M >> P;
vector<string> X(N), Y(M);
for(int j = 0; j < N; j++)
cin >> X[j];
for(int j = 0; j < M; j++)
cin >> Y[j];
// 遍历需求矩阵
vector<int> agree(P);
for(int j = 0; j < P; j++){
for(int k = 0; k < N; k++)
agree[j] += X[k][j]-48;
}
res = solve(N, M, P, Y, agree);
cout << "Case #" << i << ": " << res << endl;
}
return 0;
}