P3878 [TJOI2010]分金币【模拟退火】

洛谷P3878 -> Click Here

题意

\(n\)​ 枚硬币,第 \(i\) 枚硬币的价值为 \(v_i\) ,分为两堆,两堆硬币个数相差不能超过一,使两堆硬币价值只差最小

思路

玄学退火

在退火过程中随机改变数列中两个数字的位置,按照前半数硬币为一堆,后半数硬币为一堆的分配方法,逐渐降低温度求最优解

code

#include<iostream>
#include<cstdlib>
#include<ctime>
#include<cmath>
#define inf 2147483647
#define REP(i,a,b) for(register int i=(a);i<=(b);i++)
#define FOR(i,a,b) for(int i=(a);i<(b);i++)
using namespace std;
int n,ans=inf,a[1005];
int get(){
	int sum1=0,sum2=0;
	REP(i,1,(n+1)/2) sum1+=a[i];
	REP(i,(n+1)/2+1,n) sum2+=a[i];
	return abs(sum1-sum2);
}
void sa(){
	double beginT=5000,endT=1e-10,changeT=0.996;
	for(register double T=beginT;T>endT;T*=changeT){
		int x=rand()%n+1,y=rand()%n+1;
		swap(a[x],a[y]);
		int sum=get();
		if(sum<ans)	ans=sum;
		else if(exp((ans-sum)/T)<(double(rand())/RAND_MAX))
			swap(a[x],a[y]);
	}
}
int main(){
	srand(rand());
	int T;cin>>T;
	while(T--){
		ans=inf;
		cin>>n;
		REP(i,1,n) cin>>a[i];
		int ctrl=10;
		while(ctrl--) sa();
		cout<<ans<<endl;
	}
}
posted @ 2021-08-06 16:25  莳曳  阅读(76)  评论(0)    收藏  举报