1. 程式人生 > 其它 >P3878 [TJOI2010]分金幣【模擬退火】

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