§ Description
Link.
给一个数列和 ,在数列任选若干个数,使得他们的和对 取模后最大。
§ Solution
记录一下犯下的一个 nt 错误。
首先我们有一个显然的 DFS 暴力,每次两种决策,选或不选,所以时间复杂度为 。
的范围是 35,是过不了的,我们可以考虑折半搜索。
关于折半搜索可以看看 我的折半搜索小计。
暴力搜出 的所有答案,记录到两个 vector 里面。
这一部分的时间复杂度是 。
考虑合并贡献。
先考虑一个暴力合并贡献的方法。
我们记第一次搜索搜出来的答案序列为 ,同理有 。
这里的两个答案序列都是在模 意义下的。
那么对于每一个 ,我们都可以暴力在 中寻找两者相加模 的最大值。
那么我们可以分类讨论了,因为序列在模 意义下,所以我们对于每一个 找到的 使得 最大,都只有两种情况。
一种是 在 中值域范围在 的所有值中最大,一种是在 中值域范围在 的所有值中最大。
所以我们在这两种情况中取最大即可。
由于我不理解二分做法,所以我用的是动态开点值域线段树。
(flag:动态开点不加引用就【】)
#include<bits/stdc++.h>
using namespace std;
const int N=35+5;
const int H=99999999;
int n,m,tot=1,root=1,ans,a[N];
struct Tree
{
int ls,rs,val;
} nodes[(1<<(N>>1))<<3];
vector<int> vec[2];
void dfs(int x,int cur,int lim)
{
if(x>lim)
{
if(lim==(n>>1)) vec[0].push_back(cur);
else vec[1].push_back(cur);
return ;
}
dfs(x+1,(cur+a[x])%m,lim);
dfs(x+1,cur,lim);
}
void ins(int &p,int l,int r,int x)
{
if(p==0) p=++tot;
if(l==r)
{
nodes[p].val=l;
return ;
}
int mid=(l+r)>>1;
if(mid>=x) ins(nodes[p].ls,l,mid,x);
else ins(nodes[p].rs,mid+1,r,x);
nodes[p].val=max(nodes[nodes[p].ls].val,nodes[nodes[p].rs].val);
}
int find(int p,int l,int r,int x,int y)
{
if(l>y||r<x) return 0;
if(l>=x&&r<=y) return nodes[p].val;
int mid=(l+r)>>1,ret=0;
if(mid>=x) ret=max(ret,find(nodes[p].ls,l,mid,x,y));
if(mid<y) ret=max(ret,find(nodes[p].rs,mid+1,r,x,y));
return ret;
}
void output(int p)
{
if(nodes[p].ls==0&&nodes[p].rs==0)
{
printf("%d ",nodes[p].val);
return ;
}
output(nodes[p].ls);
output(nodes[p].rs);
}
signed main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
dfs(1,0,n>>1);
dfs((n>>1)+1,0,n);
sort(vec[0].begin(),vec[0].end());
sort(vec[1].begin(),vec[1].end());
for(auto x:vec[1]) ins(root,0,m-1,x);
for(auto x:vec[0]) ans=max(ans,max(x+find(1,0,m-1,0,m-x-1),(x+find(1,0,m-1,0,m*2-x-1))%m));
printf("%d\n",ans);
return 0;
}