[TJOI2010]分金币

洛咕

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

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

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#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
",dfs(1,0,0,0,0));
	}
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11674527.html