模拟退火
题目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);
}