专栏文章
题解:P13846 [CERC 2023] Ball Passing
P13846题解参与者 4已保存评论 3
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 3 条
- 当前快照
- 1 份
- 快照标识符
- @mio4gf6w
- 此快照首次捕获于
- 2025/12/02 13:12 3 个月前
- 此快照最后确认于
- 2025/12/02 13:12 3 个月前
P13846 [CERC 2023] Ball Passing
注:这是一篇模拟退火题解。
题目大意
有 个学生,其中男生和女生都是偶数个。
要求将男生两两配对,女生两两配对。以坐标形式给出每个学生的位置,则每对配对的学生之间会有一个欧几里得距离。
目标是最大化所有配对的距离之和。
思路
读题可知,我们只需要对于男生和女生,各自求出所有配对的距离之和的最大值即可。
那么我们先只考虑对于其中一种性别,如何最大化答案。
而对于两两配对这种问题,我们可以考虑对这种性别的学生进行重新排列,让奇数位的学生与他之后最近的偶数位的学生配对即可,即排列中的第 的学生与第 个学生配对、第 的学生与第 个学生配对、第 的学生与第 个学生配对
于是,我们只需要找到对这种性别的学生的一种排列,使得按上述方式配对后,所有配对的距离之和最大即可。
那么,在观察到数据范围 ,并且发现题目要求我们找到一种最优排列的时候,我们不妨大胆地使用模拟退火算法。
具体做法
-
先用
std::random_shuffle()把一开始给的排列打乱,防止被卡()。 -
设定一个初始温度 ,然后进入漫长的降温环节。
-
每次降温时:随机选取一种性别,在这种性别的学生中随机选择两个学生交换位置,然后求出换位置后得到的排列对应的新答案。这样子求答案是很好求的:从前往后两个两个取学生,然后将这两个学生的距离累加到新答案中即可。
-
比较新答案 和旧答案 :
-
如果新答案大于旧答案,则直接用新答案代替旧答案,且采用这个新的排列继续进行下一步的降温。
-
否则,以一定的概率采用新排列(注意这里不用新答案代替旧答案),根据算法特色,这个概率为:这样选取概率可以保证:随着 的降低,排列的变化逐渐趋于稳定;新答案比旧答案小太多时,不采用新的排列。
-
-
降温,重复执行步骤 2~5 直到温度降低到一定程度为止。
代码
C#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 51;
string S;
int n,bc=1,gc=1;//bc为男生人数,gc为女生人数
double ans;
struct obj
{
double x,y;
}b[MAXN],g[MAXN];
//分两个数组,用结构体存学生的信息,方便操作排列顺序
double getd(obj u,obj v)
{
return sqrt((u.x-v.x)*(u.x-v.x) + (u.y-v.y)*(u.y-v.y));
}
//求两个学生之间的距离
int getr(int l,int r)
{
return rand()%(r-l+1)+l;
}
//获取[l,r]的随机整数
double getp()
{
return (double)rand()/RAND_MAX;
}
//获取一个[0,1]的随机浮点数,用于概率相关计算
double getans()
{
double res = 0;
for (int i=2;i<=bc;i+=2) res += getd(b[i-1],b[i]);
for (int i=2;i<=gc;i+=2) res += getd(g[i-1],g[i]);
return res;
}
//获取当前排列状况下的答案
int main()
{
srand(time(NULL));
cin >> n >> S;
for (int i=1;i<=n;i++)
{
if (S[i-1] == 'B')
{
cin >> b[bc].x >> b[bc].y;
bc ++;
} else if (S[i-1] == 'G')
{
cin >> g[gc].x >> g[gc].y;
gc ++;
}
}
bc--,gc--;
//把男生和女生的信息分别输入到两个数组里,并统计男生和女生各自的人数
//注意字符串下标是从0开始的www
if (bc > 0) random_shuffle(b+1,b+bc+1);
if (gc > 0) random_shuffle(g+1,g+gc+1);
//对应步骤1,重排数组,注意特判某个性别学生人数为0的情况,不然可能会RE
double T = 100.0;
//对应步骤2,设定初始温度
while (T >= 0.01) //在温度低于一定数值时跳出循环
{
int s = getr(0,1),swp1,swp2;
//s表示当前选取的性别,0是女生,1是男生;swp1和swp2表示随机选取的两个学生在数组中对应的下标
if (s && bc > 0)
{
swp1 = getr(1,bc),swp2 = getr(1,bc);
swap(b[swp1],b[swp2]);
} else if (gc > 0)
{
swp1 = getr(1,gc),swp2 = getr(1,gc);
swap(g[swp1],g[swp2]);
} else continue;
//交换学生在排列中的位置,得到新的排列
//同样注意特判某个性别学生人数为0的情况
double newans = getans();
//获取新答案
if (newans <= ans && getp() > exp((newans-ans)/T))
{
if (s) swap(b[swp1],b[swp2]);
else swap(g[swp1],g[swp2]);
//这里先判断了不采用新排列的情况,不采用新排列其实就是把原来交换位置的两个学生再换回来
}
else
{
ans = max(newans,ans);
//采用新排列,且如果新答案比旧答案更大则取新答案
}
T *= 0.99999;//降温
}
cout << fixed << setprecision(6) << ans << endl;
//输出答案,精确到小数点后6位
return 0;
}
Tips: 较小时,我们可以设置初始温度较低,结束温度较高,而让降温较慢一些,这样貌似可以让更多的排列被考虑到,也更容易随机过。
退火万岁!
相关推荐
评论
共 3 条评论,欢迎与作者交流。
正在加载评论...