[TJOI2010]分金币

洛咕

题意:现在有\(n(n<=30)\)枚金币,它们可能会有不同的价值,现在要把它们分成两部分,要求这两部分金币数目之差不超过\(1\),问这样分成的两部分金币的价值之差最小是多少?

分析:模拟退火从下午写到晚上,代码交了二十几份,最好成绩40分,果断放弃,开始思考搜索.因为\(n<=30\),最暴力的搜索也就\(2^{30}\),虽然这样会超时,但是我们想办法优化一下,剪一下枝,就能过去了.剪枝就直接写注释里面了.

#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=50;
const ll inf=1e18;
ll n,tot,a[N],sum[N];
inline ll dfs(int now,int x,ll X,int y,ll Y){
//now:当前考虑到了第几个金币
//x:第一部分的金币数量;X:第一部分的金币数目之和;y,Y同理
    if(now>n)return abs(X-Y);//已经搜完了
    if(x>n/2||y>n/2)return inf;//某一个部分多了,不合法
    ll XX=X+sum[now+n/2-x-1]-sum[now-1];
    if(XX<=tot-XX)return abs(XX-(tot-XX));
//如果第一部分剩下的n/2-x个金币都选当前剩下的最大的 还是会比第二部分小,就直接这样选择了
    XX=X+sum[n]-sum[n+x-n/2];
    if(XX>=tot-XX)return abs(XX-(tot-XX));
//如果第一部分剩下的n/2-x个金币都选当前剩下的最小的 还是会比第二部分大,也直接这样选择了
    ll p=dfs(now+1,x+1,X+a[now],y,Y);//当前金币给第一部分
    ll q=dfs(now+1,x,X,y+1,Y+a[now]);//当前金币给第二部分
    return min(p,q);
}
int main(){
    int T=read();
    while(T--){
        tot=0;n=read();//刚开始忘记tot=0,WA了几发
        for(int i=1;i<=n;++i)a[i]=read(),tot+=a[i];
        if(n&1)a[++n]=0;sort(a+1,a+n+1);reverse(a+1,a+n+1);
        for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
        printf("%lld\n",dfs(1,0,0,0,0));
    }
    return 0;
}

你可能感兴趣的:([TJOI2010]分金币)