题目文本
Description
kk在刷题劳累的时候喜欢去曲院风荷听戏喝茶。kk有一个容量为x个单位的大茶杯,同时kk想喝上一下午,于是他买了n杯茶,每杯茶中有ai个单位的茶水。但是kk有个坏习惯,就是当茶杯里的水倒满的时候kk会倒掉茶杯里的所有茶水(缓慢倒水,水满就倒掉),例如假设往空茶杯中总共加了m个单位的茶水,茶杯中只会剩余m%x单位的茶水。现在kk想知道将n杯茶水选取任意杯茶水倒入茶杯中,最后最多能剩下多少茶水。但是kk太劳累了,所以需要你来帮忙计算剩余茶水的最大值。
Input
单组输入。
每组输入的第一行给出数字n, x ,分别代表有n杯茶水和茶杯的容量x单位(1 <=n,<=35, 1<=x<=1000000000)。
接下来一行有n个数字,a1,a2,a3……an代表n杯茶含有的茶水量,(1<=ai<=1000000000)。
Output
输出仅一行,为一个整数t,代表茶杯能剩下的水最多有多少单位。
input
4 5
1 2 3 4
output
4
input
5 100
9 21 5645 48 6
output
99
解题思路
题意: 在n
杯水中选取几杯倒入一个大茶杯,如果水量超过(含等于)大茶杯的容量则取余。要我们求得能达到的最大水量。
题解: 那这样的题目我们很容易想到DFS,递归中两种选择,第i
杯水可以选择倒入大茶杯或者不倒入。看上去很简单,但是在于水杯可能多达35杯,每次两种选择,下来就是 235 种方案,方案显然过多了。我也是亲自试验过了的,直接用DFS做会TLE,改成BFS做会MLE。
对于这样的情况,我们可以选择折半搜索,即把35杯拆分成17杯和18杯。218 就在能接受的范围了,然后再拿17杯所产生的方案从后18杯产生的方案中寻找一个最优方案相加得到一个新的方案。
我们可以先思考一下这种做法的可行性。首先我们得保证的是,将水杯拆分了以后,我们需要保存下两边能得到的所有方案(因为单独哪一边都是不完整的,需要加上另一边的方案)。两边的方案数相乘其实就是原本应该有的总方案数了,但是当然不可以这么做了,这样还得超时。所以我们的办法就是,遍历其中一边的所有方案,然后从另一边中找到一个合适的方案,两者相加,生成一个新的方案。
那就要问了,什么叫合适的方案,就是方案1 + 方案2 < 大茶杯容量x
,那么方案1和方案2就互为合适的方案,因为两者相加如果大于等于总容量的话,是要取余的,那么数值可能还变小了。当然,我们还要尽可能在合适的方案中,找到一个最大的方案。所以我们可以先按水量排好序,然后通过二分找到最合适的方案,相加得到新方案。
基本思路:Ⅰ将所有水杯拆分成两份,分别用DFS求出所有方案并保存其值在S1和S2中;Ⅱ给S2排序,然后遍历S1中的所有方案,都从S2中找到对应的最合适的方案相加得到新的方案;Ⅲ最后的答案就是在相加的新方案中选取最大值。
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6;
int n, x, m, cnt1, cnt2, max_sum;
int cup[36], t[maxn], s[maxn];
void dfs(int i, int sum){
max_sum = max(max_sum, sum);
t[cnt1++] = sum; //记录方案的数值
if (i == n / 2) return;
dfs(i + 1, (sum + cup[i]) % x); //选择倒入这杯水
dfs(i + 1, sum); //选择不倒入这杯水
}
void DFS(int i, int sum){
max_sum = max(max_sum, sum);
s[cnt2++] = sum;
if (i == n) return;
DFS(i + 1, (sum + cup[i]) % x);
DFS(i + 1, sum);
}
int serch(int l, int r, int x){ //二分查找“最合适”的方案
if(l == r) return s[l];
int mid = (l + r) / 2;
if(s[mid] > x) return serch(mid + 1, r, x);
return serch(l, mid, x);
}
bool cmp(int a, int b){ return a > b;}
int main(){
scanf("%d%d", &n, &x);
for (int i = 0; i < n; i++) scanf("%d", cup + i);
dfs(0, 0); DFS(n / 2, 0); //分为两个模块进行DFS
sort(s, s + cnt2, cmp); //给其中一个模块排序
for (int i = 0; i < cnt1; i++){
int tem = serch(0, cnt2, x - t[i] - 1);
int sum = (t[i] + tem) % x;
max_sum = max(max_sum, sum);
}
printf("%d\n", max_sum);
return 0;
}
考虑去重的代码
如果用上一段代码的话,我们可以尝试输出所有方案,会发现有一半的数值显示0,还有很多很多的重复,当然因为我们已经折半过了,所以数据不会太大,重复了也不要紧。但如果下次的题目,故意恶心你,故意在这里卡你怎么半,其实是有办法优化的,但我也提交了以后对比过,用set去重,是以牺牲时间为代价的。原来的代码是比较快,但同样也牺牲了空间。所以就根据题目自己权衡吧。
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
const int maxn = 1e6;
int n, x, m;
int cnt1, cnt2;
int max_sum;
int cup[36];
int t[maxn], s[maxn];
set<int> the, shy;
void dfs(int i, int sum){
sum %= x;
max_sum = max(max_sum, sum);
if(the.find(sum) == the.end()){
the.insert(sum);
t[cnt1++] = sum;
}
if (i == n / 2) return;
dfs(i + 1, sum + cup[i]);
dfs(i + 1, sum);
}
void DFS(int i, int sum){
sum %= x;
max_sum = max(max_sum, sum);
if(shy.find(sum) == shy.end()){
shy.insert(sum);
s[cnt2++] = sum;
}
if (i == n) return;
DFS(i + 1, sum + cup[i]);
DFS(i + 1, sum);
}
int serch(int l, int r, int x){
if(l == r) return s[l];
int mid = (l + r) / 2;
if(s[mid] > x) return serch(mid + 1, r, x);
else return serch(l, mid, x);
}
bool cmp(int a, int b){ return a > b;}
int main(){
cin >> n >> x;
for (int i = 0; i < n; i++){
cin >> cup[i];
cup[i] %= x;
}
dfs(0, 0);
DFS(n / 2, 0);
sort(t, t + cnt1, cmp);
sort(s, s + cnt2, cmp);
for (int i = 0; i < cnt1; i++){
int tem = serch(0, cnt2, x - t[i] - 1);
int sum = (t[i] + tem) % x;
max_sum = max(max_sum, sum);
}
cout << max_sum << endl;
return 0;
}