HZNU2509 曲院风荷——折半搜索

题目文本

传送门

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值