题意:给定\(n(n<=35)\)个数的数列和\(m(m<=1e9)\),在数列中任选若干个数,使得他们的和对\(m\)取模后最大,求这个最大值.
分析:看到这个\(35\),很容易想到折半搜索.把数列分成相等的两个部分,分别爆搜出每个部分所有能够产生的和(在模\(m\)的意义下),时间复杂度为\(2^{18}\),很稳.
然后对于前半部分的一个数\(x\),它要么是跟后半部分最大的那个与之相加不超过\(m\)的数\(y\)产生最大答案,要么是跟后半部分最大的那个数\(q2[sum2]\)产生最大答案.
所以我们只要考虑如何求第一种情况,显然可以把两个部分的数从小到大排序,然后两个指针来尺取就好了.
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=600005;
int n,m,tot1,tot2,a[40],q1[N],q2[N];
inline void dfs1(int now,ll sum){
q1[++tot1]=sum;if(now>n/2)return;
dfs1(now+1,(sum+a[now])%m);dfs1(now+1,sum);
}
inline void dfs2(int now,ll sum){
q2[++tot2]=sum;if(now>n)return;
dfs2(now+1,(sum+a[now])%m);dfs2(now+1,sum);
}
int main(){
n=read();m=read();for(int i=1;i<=n;++i)a[i]=read()%m;//一开始就可以取模
dfs1(1,0);dfs2(n/2+1,0);//折半搜索1~n/2和n/2+1~n
sort(q1+1,q1+tot1+1);sort(q2+1,q2+tot2+1);//两部分从小到大排序
int sum1=unique(q1+1,q1+tot1+1)-q1-1;
int sum2=unique(q2+1,q2+tot2+1)-q2-1;//去重
int ans=max(q1[sum1],q2[sum2]);//可能的最大答案
if(ans==m-1){printf("%d\n",ans);return 0;}//如果已经是最优解了就直接输出
int l=0,r=sum2,L=sum2;//q1[1]=0,不用考虑了.从q1[2]开始考虑
while(l<=r){
int mid=(l+r)>>1;
if(q2[mid]+q1[2]>=m){
L=mid;r=mid-1;
}
else l=mid+1;
}
ans=max(ans,max(q1[2]+q2[L-1],(q1[2]+q2[sum2])%m));//q1[2]可能产生的最大答案
for(int i=3;i<=sum1;++i){
while(L>=2&&q2[L]+q1[i]>=m)--L;
ans=max(ans,max(q1[i]+q2[L],(q1[i]+q2[sum2])%m));
}
printf("%d\n",ans);
return 0;
}