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;
}

posted @ 2018-11-29 22:38  nianheng  阅读(172)  评论(0编辑  收藏  举报