Luogu-3878 [TJOI2010]分金币
这题和在我长郡考试时的一道题思路差不多...考虑折半搜索,预处理左半边选的方案所产生的数量差值\(x\)以及价值差值\(y\),把\(y\)扔到下标为\(x\)的set里面,然后在搜索右半边,每搜出一个状态,设他的数量差值为\(a\),价值差值\(b\),根据题意,要满足数量差值小于1,就要找左半边的状态来互补一下,很显然,如果\(n\)是偶数,数量差就一定是0,否则可以是正负1,所以要在\(set[-a]\)或\(set[-a-1],set[-a+1]\)里二分找一个数\(c\)使他加\(b\)最小,答案去绝对值最小值就好了。注意下标要统一加\(n\),防止出现负值。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef set<int> ST;
const int maxn=1<<15;
int t,n,a[maxn],ans;
ST st[110];
void ycl(){
for(int i=0;i<=109;i++)
st[i].clear();
ans=0x7fffffff;
int m=n/2,lim=1<<m;
for(int i=0;i<lim;i++){
int cnt=0,tot=0;
for(int j=1,k=1;j<=m;j++,k<<=1)
if(k&i) cnt++,tot+=a[j];
else cnt--,tot-=a[j];
st[cnt+n].insert(tot);
}
}
int check(int x,int y){
int ans=0x7fffffff;
ST::iterator p=st[x].lower_bound(y);
ST::iterator q=st[x].upper_bound(y);
if(p!=st[x].end()) ans=min(ans,abs(*p-y));
if(q!=st[x].end()) ans=min(ans,abs(*q-y));
return ans;
}
void work(){
int l=n/2;
int m=n-l,lim=1<<m;
for(int i=0;i<lim;i++){
int cnt=0,tot=0;
for(int j=1,k=1;j<=m;j++,k<<=1)
if(k&i) cnt++,tot+=a[l+j];
else cnt--,tot-=a[l+j];
cnt=n-cnt;
if(n%2)
ans=min(ans,min(check(cnt-1,-tot),check(cnt+1,-tot)));
else
ans=min(ans,check(cnt,-tot));
}
}
int main(){
// freopen(".in","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
ycl();
work();
printf("%d\n",ans);
}
return 0;
}