Description

给定 \(n\leq 35\) 堆石子,可以把一些堆合并。合并之后,石子个数为这些堆石子个数和模 \(Mod\),求最终能得到的最大的石子个数。

Solution

最多有 \(2^n\) 种可能的石子个数,直接搜索不行。注意到背包的可合并性质,我们折半搜索。把石子分为两边,分别处理出所有可能值,这一步 \(O(2^{\frac{n}{2}})\)。然后考虑合并,两堆石子 \(0\leq a+b <2\times Mod\),对所有值排序,分类讨论,枚举 \(a\),找到最大的 \(b\),使得 \(a+b<Mod\),用这个更新答案,之后可以用 two-pointer 解决。对于 \(a+b>Mod\) 的值,最后的值是 \(a+b-Mod\) 是小于 \(Mod\) 的,所以直接贪心地取最大的 \(b\) 更新答案。

#include<stdio.h>
#include<algorithm>
using namespace std;
const int N=20;
const int M=1<<N;
int L,Mod,A[N],B[N],n,m,a[M],b[M];
void dfs(int *X,int *w,int st,int ed,int s){
if(st==ed) w[++w[0]]=s;
else dfs(X,w,st+1,ed,s),
dfs(X,w,st+1,ed,(s+X[st])%Mod);
}
int main(){
freopen("stone.in","r",stdin);
freopen("stone.out","w",stdout);
scanf("%d%d",&L,&Mod);
n=L/2; m=L-n; int ans=0;
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<=m;i++) scanf("%d",&B[i]);
dfs(A,a,1,n+1,0),dfs(B,b,1,m+1,0);
sort(a+1,a+1+a[0]),sort(b+1,b+1+b[0]);
for(int i=1;i<=b[0];i++) ans=max(ans,b[i]);
for(int i=1;i<=a[0];i++) ans=max(ans,a[i]);
int lt=1; while(lt<=b[0]&&a[1]+b[lt]<Mod) lt++; lt--;
for(int i=1;i<=a[0];i++){
while(lt&&b[lt]+a[i]>=Mod) lt--;
ans=max(ans,a[i]+b[lt]);
ans=max(ans,(a[i]+b[b[0]])%Mod);
}
printf("%d",ans);
}