P3878 [TJOI2010]分金幣【模擬退火】
阿新 • • 發佈:2021-08-06
洛谷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; } }