Codeforces 888E(位运算+meet-in-the-middle)

E. Maximum Subsequence

You are given an array a consisting of n integers, and additionally an integer m. You have to choose some sequence of indices b1, b2, …, bk (1 ≤ b1 < b2 < … < bk ≤ n) in such a way that the value of is maximized. Chosen sequence can be empty.

Print the maximum possible value of .

Input
The first line contains two integers n and m (1 ≤ n ≤ 35, 1 ≤ m ≤ 109).

The second line contains n integers a1, a2, …, an (1 ≤ ai ≤ 109).

Output
Print the maximum possible value of .

Examples
input
4 4
5 2 4 1
output
3
input
3 20
199 41 299
output
19

题目大意:给n k。有n个数,求从中选任意个数加和对k取模后的最大值。
首先想到的是二进制枚举所有选择的情况。但是遇到的问题就是n最大为35。枚举的方案数最多为2^35。1s是绝对跑不完的。于是这次就学到了一个很有意思的算法meet-in-the-middle。
meet-in-the-middle是一种对多个数进行枚举或分类讨论时,可以先对序列的前一半进行枚举,之后再对后一半序列进行枚举并维护和前一半情况相结合的最优结果。
这种算法将原本的O(n^m)降为了O(n^(m/2)),在对于m比较大的情况时有奇效。

本题也就是对于n==35的情况我们,先枚举了1<<18种方案(262144),然后枚举后半部分1<<17种方案,并对每种方案和前半部分匹配,维护最大值。在维护答案时如果用遍历的话会效率太低也会爆,所以我们可以用二分去查找来优化。而用二分的话我们需要对前半部分的枚举情况进行排序。end

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const int maxn = 1<<18+5;
LL a[40],pre[maxn],las,ans,mod;
int tol;
LL found(int l,int r,LL x)  //二分查找进行匹配
{
    if(l==r) return max(x+pre[l],x+pre[tol-1]-mod);
    if(l==r-1)
    {
        if(x+pre[r]>=mod) return max(x+pre[l],x+pre[tol-1]-mod);
        else  return max(x+pre[r],x+pre[tol-1]-mod);
    }
    int mid = (l+r)/2;
    if(x+pre[mid]>=mod) return found(l,mid,x);
    else return found(mid,r,x);
}
int main()
{
    int n;
    memset(pre,0,sizeof pre);
    ans=0;
    scanf("%d%I64d",&n,&mod);
    for(int i=0;i<n;i++){
        scanf("%I64d",&a[i]);
        a[i]%=mod;
    }
    /*input*/
    int mid = n/2;
    tol = 1<<mid;
    for(int i=0;i<tol;i++)
    {
        for(int j=0;j<mid;j++)
        {
            if((i>>j)&1)
                pre[i] = (pre[i]+a[j])%mod;
        }
    }
    sort(pre,pre+tol);
    /*前半部分枚举+排序*/
    mid = n-mid;
    int maxm = 1<<mid;
    for(int i=0;i<maxm;i++)
    {
        las = 0;
        for(int j=0;j<mid;j++)
        {
            if((i>>j)&1)
                las = (las+a[j+n-mid])%mod;
        }
        ans = max(ans,found(0,tol-1,las));
    }
    /*后半部分枚举+二分维护最大值*/
    printf("%I64d\n",ans);
    /*output*/

}

缺点,二分部分仍然掌握的不够熟练,写二分的时候思路不够清晰。递归出口是二次修正之后的,可以再看看能不能继续优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值