1. 程式人生 > >分金幣----拿模擬退火來做是不是有點過分了

分金幣----拿模擬退火來做是不是有點過分了

題目描述

圓桌上坐著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;
}