模拟退火

模拟退火

题目1:Acwing 3167. 星星还是树(最小化结果)

分析:

模板题

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//三年竞赛一场空,不开long long见祖宗
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
#define pb(a) push_back(a)
#define x first
#define y second
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int n;
pdd a[maxn];
double ans = 1e18;

double rand(double l, double r)
{
    return (double)rand() / RAND_MAX * (r - l) + l;
}

double dis(pdd a, pdd b)
{
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

double calc(pdd now)
{
    double res = 0;
    for(int i = 1; i <= n; i++)
        res += dis(now, a[i]);
    ans = min(ans, res);
    return res;
}

void simulate()
{
    pdd cur(rand(0, 10000), rand(0, 10000));
    for(double t = 1e4; t >= 0.1; t *= 0.9)
    {
        pdd now(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t));
        double delta = calc(now) - calc(cur);
        if(exp(-delta / t) > rand(0, 1))
            cur = now;				//如果更优则转移
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y;
    for(int i = 1; i <= 100; i++) //while((double)clock() / CLOCKS_PER_SEC <= 0.8) simulate();
        simulate();
    printf("%.0lf\n", ans);
}

题目2:Acwing 2424. 保龄球 (最大化结果)

JYY 很喜欢打保龄球,虽然技术不高,但是还是总想着的高分。

这里 JYY 将向你介绍他所参加的特殊保龄球比赛的规则,然后请你帮他得到尽量多的分数。

一场保龄球比赛一共有 N 个轮次,每一轮都会有 10 个木瓶放置在木板道的另一端。

每一轮中,选手都有两次投球的机会来尝试击倒全部的 10 个木瓶。

对于每一次投球机会,选手投球的得分等于这一次投球所击倒的木瓶数量。

选手每一轮的得分是他两次机会击倒全部木瓶的数量。

对于每一个轮次,有如下三种情况:

“全中”:如果选手第一次尝试就击倒了全部 10 个木瓶,那么这一轮就称为“全中”。在一个“全中”轮中,由于所有木瓶在第一次尝试中都已经被击倒,所以选手不需要再进行第二次投球尝试。同时,在计算总分时,选手在下一轮的得分将会被乘 2 计入总分。
“补中”:如果选手使用两次尝试击倒了 10 个木瓶,那么这一轮就称为“补中”。同时,在计算总分时,选手在下一轮中的第一次尝试的得分将会被乘以 2 计入总分。
“失误”:如果选手未能通过两次尝试击倒全部的木瓶,那么这一轮就被称为“失误”。同时,在计算总分时,选手在下一轮的得分会被计入总分,没有分数被翻倍。
此外,如果第 N 轮是“全中”,那么选手可以进行一次附加轮:也就是,如果第 N 轮是“全中”,那么选手将一共进行 N+1 轮比赛。

显然,在这种情况下,第 N+1 轮的分数一定会被加倍。

附加轮的规则只执行一次。

也就是说,即使第 N+1 轮选手又打出了“全中”,也不会进行第 N+2 轮比赛。

因而,附加轮的成绩不会使得其他轮的分数翻番。

最后,选手的总得分就是附加轮规则执行过,并且分数按上述规则加倍后的每一轮分数之和。

JYY 刚刚进行了一场 N 个轮次的保龄球比赛,但是,JYY 非常不满意他的得分。

JYY 想出了一个办法:他可以把记分表上,他所打出的所有轮次的顺序重新排列,这样重新排列之后,由于翻倍规则的存在,JYY 就可以得到更高的分数了!

当然了,JYY 不希望做的太假,他希望保证重新排列之后,所需要进行的轮数和重排前所进行的轮数是一致的:

比如如果重排前 JYY 在第 N 轮打出了“全中”,那么重排之后,第 N 轮还得是“全中”以保证比赛一共进行 N+1 轮;同样的,如果 JYY 第 N 轮没有打出“全中”,那么重排过后第 N 轮也不能是全中。

请你帮助 JYY 计算一下,他可以得到的最高的分数。

输入格式
第一行包含一个整数 N,表示保龄球比赛所需要进行的轮数。

接下来包含 N 或者 N+1 行,第 i 行包含两个非负整数 Xi 和 Yi,表示 JYY 在这一轮两次投球尝试所得到的分数,Xi 表示第一次尝试,Yi 表示第二次尝试。

我们用 10 0 表示一个“全中”轮。

输入数据保证合法,当且仅当 Xn=10,Yn=0 时,存在 N+1 行 Xi 和 Yi。

输出格式
输出一行一个整数,表示 JYY 最大可能得到的分数。

数据范围
1≤N≤50
输入样例:
2
5 2
10 0
3 7
输出样例:
44
样例解释
按照输入顺序,JYY 将得到 37 分。

最佳方案是将 3 个轮次排列成如下顺序:

3 7
10 0
5 2

分析:

随机交换两个位置(交换要合法,即要保证轮次不能变),如果分数更大则一定转移,否则有可能转移,温度t用在转移时的判断。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//三年竞赛一场空,不开long long见祖宗
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
#define pb(a) push_back(a)
#define x first
#define y second
typedef pair<int, int> pii;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
pii a[maxn];
int n, m;
int ans;

int calc()
{
    int res = 0;
    for(int i = 1; i <= m; i++)
    {
        res += a[i].x + a[i].y;
        if(i <= n)
        {
            if(a[i].x == 10) res += a[i + 1].x + a[i + 1].y;
            else if(a[i].x + a[i].y == 10) res += a[i + 1].x;
        }
    }
    ans = max(ans, res);
    return res;
}

void simulate()
{
    for(double t = 1e4; t >= 1e-4; t *= 0.99)
    {
        int pos1 = rand() % m + 1, pos2 = rand() % m + 1;
        int last = calc();
        swap(a[pos1], a[pos2]);			//先交换
        if(n + (a[n].x == 10) == m)
        {
            int now = calc();			//计算
            swap(a[pos1], a[pos2]);		//换回

            int delta = now - last;
            if(exp(delta / t) > 1.0 * rand() / RAND_MAX)   //如果结果更差,则也有一定概率转移
                swap(a[pos1], a[pos2]); 		//如果更优则真的交换
        }
        else swap(a[pos1], a[pos2]);  			//不合法则换回来
    }   
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y;
    if(a[n].x == 10) cin >> a[n + 1].x >> a[n + 1].y, m = n + 1;
    else m = n;
    for(int i = 1; i <= 100; i++)
        simulate();
    // while((double)clock() / CLOCKS_PER_SEC <= 0.8) simulate();
    cout << ans << endl;
}

题目3:2680. 均分数据(最小化结果)

分析:

我们可以用random_shuffle()用来对原数据进行重新排序,然后利用贪心的思想(每次把数加在最小的组里),即可满足均方差最小。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//三年竞赛一场空,不开long long见祖宗
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
#define pb(a) push_back(a)
#define x first
#define y second
typedef pair<int, int> pii;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const int maxn = 1e2 + 10;
const int inf = 0x3f3f3f3f;
int n, m;
int a[maxn], sum[maxn];
double ans = 1e8;

double calc()
{
    mem(sum, 0);
    for(int i = 1; i <= n; i++)
    {
        int pos = 1;
        for(int j = 1; j <= m; j++)
            if(sum[j] < sum[pos])
                pos = j;
        sum[pos] += a[i];
    }
    double avg = 0;
    for(int i = 1; i <= m; i++) avg += sum[i];
    avg /= m;
    double res = 0;
    for(int i = 1; i <= m; i++)
        res += (sum[i] - avg) * (sum[i] - avg);
    res = sqrt(res / m);
    ans = min(ans, res);
    return res;
}

void simulate()
{
    random_shuffle(a + 1, a + 1 + n);
    for(double t = 1e4; t > 1e-4; t *= 0.98)
    {
        int pos1 = rand() % n + 1, pos2 = rand() % n + 1;
        double last = calc();
        swap(a[pos1], a[pos2]); //交换
        double now = calc();	//计算
        double delta = now - last;
        swap(a[pos1], a[pos2]);	//换回
        if(exp(-delta / t) > (double)rand() / RAND_MAX)
            swap(a[pos1], a[pos2]);	//更优则真交换
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= 100; i++) simulate();
    printf("%.2lf\n", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值