1. 程式人生 > >Luogu-3878 [TJOI2010]分金幣

Luogu-3878 [TJOI2010]分金幣

註意 printf urn per 偶數 ... mat cpp work

這題和在我長郡考試時的一道題思路差不多...考慮折半搜索,預處理左半邊選的方案所產生的數量差值\(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;
}

Luogu-3878 [TJOI2010]分金幣