分金币----拿模拟退火来做是不是有点过分了

题目描述

圆桌上坐着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上提交的代码

 

原来的模拟退火貌似没用那个什么什么准则,不太懂,但是能A题,现在换了个用那个准则的代码

对了,这个代码在洛谷上可能会T,把k或delta调小点就行了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const double Esp = 1e-2,inf = 1e18,delta = 98e-2;
const int k = 10,maxn=1e6+7;
ll a[maxn],n,sum,b[maxn];
double Rand(){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;
}
ll Sovle(){
    ll t = 1e9,ans = inf;
    ll x = Rand()*1e9,tx;
    ll f1 = Fun(x),f2,df;
    double r;
    while(t>0){
        for(int i=0;i<k;i++){
            tx = x + Rand()*t;
            if( tx + 1e9 >= 0 && tx - 1e9 <= 0){
                f2 = Fun(tx);
                df = f2 - f1;
                if(df <= 0){
                    r = fabs(Rand());
                    if(exp(1.0*df/t) <= r){
                        f1 = f2;
                        x = tx;
                    }
                }
            }
            ans = min(ans,f1);
        }
        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;
}

 

 

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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值