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*/
}
缺点,二分部分仍然掌握的不够熟练,写二分的时候思路不够清晰。递归出口是二次修正之后的,可以再看看能不能继续优化