LGOJ P3878 [TJOI2010]分金币 Solution

该博客介绍了一种使用模拟退火算法解决分金币问题的方法,旨在找到使两部分金币价值差最小的方案。文章详细阐述了算法的思路、实现步骤以及参数调整的重要性,并提供了具体的算法应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当然是退火啦!QwQ

题目描述

现在有n枚金币,它们可能会有不同的价值,现在要把它们分成两部分,要求这两部分金币数目之差不超过1,问这样分成的两部分金币的价值之差最小是多少?

输入输出格式

输入格式:

每个输入文件中包含多组测试数据,输入文件的第一行是一个正整数T(T≤20)T(T≤20)T(T20),用来说明文件中有多少组测试数据。接下来将依次给出所有测试数据的描述,每组测试数据的第一行是一个正整数n(n≤20)n(n≤20)nn20,表示共有nnn枚金币。第二行有nnn个正整数viv_ivi,分别给出每一枚金币的价值。

输出格式:

对每一组输入数据,输出一个非负整数,表示分成的两部分金币的价值差别的最小值。

分析:

搜索当然是可以的,但是也要考虑到省选的进程(大雾),毕竟考场上写dfs这东西,万一写错了点什么,又不知道是写挂了死循环还是真的TLE掉了。而且,此题dfs剪枝并不是十分理想,虽然可以做,但是效果不好。

对于最优化问题,我们当然可以立刻想到一个方法,即随机的分配这些金币,统计两组的差值并加以修改,但我们可以意识到这种修改得出的答案往往不令人满意。

为了平衡时间消耗巨大和答案不准确的两种算法,我们考虑采用随机化搜索的一种——模拟退火算法。这种算法模仿了金属退火降温的规律,能够在给定的时间内得出一组较优的解。并且,大部分情况下能够给出确切地最优解。

模拟退火算法:
基本实现步骤:
  1. 生成一组随机的解AnsiAns_iAnsi
  2. 对于新生成的解,求出与当前解差值δ=Ansi−Ansnow\delta=Ans_i-Ans_{now}δ=AnsiAnsnow
  3. 如果δ&lt;0,\delta&lt;0,δ<0,表示这组解更加优秀,我们无条件地接受。否则我们以一个固定概率接受,这个概率P=e−δkTP=e^{-\frac{\delta}{kT}}P=ekTδ,T 是当前的温度,k是玻尔兹曼常数。不难看出,T减少(降温)后,我们要让接受非最优解的概率降低,因为系统需要趋于稳定和能量最低
  4. 降温,将T缩小固定的大小。

在本题中我们按照这个套路走下去,首先随机生成一个金币序列,将前[n2][\frac{n}{2}][2n]个金币分派给组A,n−[n2]n-[\frac{n}{2}]n[2n]个金币分配给组B。每次随机交换两组中各一个金币,统计这两组金币价值的差δ\deltaδ。然后我们按照上述准则选择是否接受这个答案。

  • 在程序实现时,为了调控这个概率,我们省去了玻尔兹曼常数,在左边乘上INT_MAX,在右边用一个随机生成的数字表示概率。如果随机数大于左边的参数,那么我们接受,否则,我们拒绝。
  • 可以看出,随着T的减小左边参数越来越大,接受非最优解的概率也越来越小。

最后不要忘记降温。而降温也是个玄学过程。我们推荐使用等比方式降温,设置降温速度e(e&lt;1)e(e&lt;1)e(e<1).每次取舍后让T=eTT=eTT=eT.至于e,我们要根据具体题目选择。e比较大,答案精确,耗时较大;e比较小,答案未必精确,但运行速度快。

本题中我们选择T=2048,e=0.998,末温eps=1e−7T=2048,e=0.998,末温eps=1e-7T=2048,e=0.998,eps=1e7,并对每组数据分别退火14次,即可通过本题。当然,退火次数可能更少些,或者其他参数调整也可以通过。

  • 参数的调整本身也是模拟退火的重要部分。当你Wa掉的时候不一定是算法不对,很可能只是参数不优秀。因此,可以修改参数多次提交代码以获得高分。当然,模拟退火在IOI和ACM中用武之地可能更大些,至于OI,如果真的走投无路并且相信自己的人品,那么,就放心地退火吧!

代码

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define maxn 50
#define eps 1e-7
#define ll long long 
using namespace std;
inline ll read()
{
    ll n=0,k=1; 
    char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') k=-1; c=getchar();}
    while(c>='0'&&c<='9') n=(n<<1)+(n<<3)+(c^48),c=getchar();
    return n*k;
}
int n,m;
int T;
ll arr[maxn];
ll ans=99999999999;
inline int count() 
{
    int res1=0,res2=0;
    for (register int i=1;i<=n;++i)
        if (i<=(n+1)/2) res1+=arr[i];
        else res2+=arr[i];
    return abs(res1-res2);
}

inline void SA()
{
    double Tmp =2048.0;
    while(Tmp>eps)
    {
        int s1=rand()%((n+1)/2)+1,s2=rand()%((n+1)/2)+((n+1)/2);
        if (s1<=0||s1>n||s2<=0||s2>n) continue;
        swap(arr[s1],arr[s2]);
        int newans=count();
        int del=ans-newans;
        if (del>0) ans=newans;
        else if (exp((double)((double)del/Tmp))*RAND_MAX<=rand()) swap(arr[s1],arr[s2]);
        Tmp*=0.998;
    }
}
int main()
{
    srand(time(NULL));
    T=read();
    while(T--)
    {
        ans=99999999999;
        n=read();
        for(register int i=1;i<=n;++i)
            arr[i]=read();
        random_shuffle(arr+1,arr+n+1);
        for(register int i=1;i<=2;++i)
            SA(),SA(),SA(),SA(),SA(),SA(),SA();
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值