分金幣----拿模擬退火來做是不是有點過分了
阿新 • • 發佈:2018-11-19
題目描述
圓桌上坐著n個人,每人有一定數量的金幣,金幣總數能被n整除。每個人可以給他左右相鄰的人一些金幣,最終使得每個人的金幣數目相等。你的任務是求出被轉手的金幣數量的最小值。
輸入輸出格式
輸入格式:
第一行為整數n(n>=3),以下n行每行一個正整數,按逆時針順序給出每個人擁有的金幣數。
輸出格式:
輸出被轉手金幣數量的最小值。
輸入輸出樣例
輸入樣例#1: 複製
4 1 2 5 4
輸出樣例#1: 複製
4 樣例解釋 設四個人編號為1,2,3,4。第3個人給第2個人2個金幣(變成1,4,3,4),第2個人和第4個人分別給第1個人1個金幣。
說明
N<=<=100000,總金幣數<=10^9
因為太菜,所以並沒有找出來中位數,拿模擬退火寫的,中石油,ZCMU,洛谷(糖果傳遞那個題的資料太強的,氵不過去,只能用中位數)都過了
ZCMU上的是多組輸入,所以這裡是最後在ZCMU上提交的程式碼
#include<bits/stdc++.h> using namespace std; typedef long long ll; const double Esp = 1e-2,inf = 1e18,delta = 93e-2; ///Esp-->精度,initT-->初始步長,inf-->最大值,delta-->步長每次縮短的係數 const int k = 3,maxn=1e6+7; ///每次隨機跑動幾次 ll a[maxn],n,sum,b[maxn]; double Rand(){///隨機數輸出一個概率範圍是[-1,1] return rand()&1 ? 1.0*rand()/RAND_MAX : -1.0*rand()/RAND_MAX; } ll Fun(ll x){ double ans = fabs(x); for(int j=2;j<=n;j++) x += a[j] - sum,ans += fabs(x); return ans; /*double ans = 0; for(int j=0;j<n;j++) ans += abs(x-b[j]); return ans;*/ } ll Sovle(){///模擬退火 ll t = 1e9,ans = inf;///t-->初始溫度(步長),ans-->答案 ll x = Rand()*1e9;///生成原始解,[0,100] while(t>0){///步長咯 ll tfx = Fun(x);///函式值 for(int i=0;i<k;i++){///隨機瞎跑,取最優解 ll tx = x + Rand()*t;///跑動範圍(delta x) [-t,t],即最大步長內 if( tx + 1e9 >= 0 && tx - 1e9 <= 0){///在[0,100]範圍內 ll ttfx = Fun(tx);///這次跑出去得到的函式值 if(ttfx < tfx){///如果比較優秀 tfx = ttfx; x = tx; } } ans = min(ans,tfx);///更新答案 } t *= delta;///步長縮短 } return ans; } int main(){ while(~scanf("%lld",&n)){ sum = 0; for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum += a[i]; sum /= n; printf("%lld\n",Sovle()); } return 0; }