AcWing进阶算法课Level-4 第六章 搜索 (模拟退火,爬山)
Posted 小哈里
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AcWing进阶算法课Level-4 第六章 搜索 (模拟退火,爬山)相关的知识,希望对你有一定的参考价值。
AcWing进阶算法课Level-4 第六章 搜索
模拟退火
AcWing 3167. 星星还是树110人打卡
AcWing 2424. 保龄球78人打卡
AcWing 2680. 均分数据72人打卡
爬山法
AcWing 207. 球形空间产生器51人打卡
代码
AcWing 3167. 星星还是树
/* 模拟退火
功能:当一个问题的方案数量极大(甚至是无穷的)而且不是一个单峰函数时,我们难以准确求出具体的解,可以通过多次迭代,它可以不断地接近最优解。
实现:在某一给定初温下,通过缓慢下降温度参数,使算法能够在多项式时间内给出一个近似最优解(对热力学退火过程的模拟)
概述:如果新状态的解更优则修改答案,否则以一定概率接受新状态。
*/
//题意:给出n个点的坐标(整数),找出到这n个点距离和最小的位置,输出距离和。
//思路:每次随机一个[0,1e4]的点,判断是否比新解更优。
#include<bits/stdc++.h>
using namespace std;
struct node{double x, y;};
vector<node>a;
double ans = 1e9+10; //最终答案
double rand(double l, double r){return (double)rand()/RAND_MAX*(r-l)+l;}
double getd(node x, node y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}
double getsum(node x){
double res = 0;
for(node t : a)res += getd(t,x);
ans = min(ans, res);
return res;
}
void sa(){
node p = node{rand(0,1e4),rand(0,1e4)};//随机一个初始解
for(double t=1e4; t > 1e-4; t*=0.9){ //初始温度T0,终止温度Tk,降温系数d
node np = node{rand(p.x-t,p.x+t),rand(p.y-t,p.y+t)};//随机产生新的答案
double dt = getsum(np)-getsum(p);
if(dt<0)p = np; //如果此答案更优,就接受
else if(exp(-dt/t)>rand(0,1))p=np; //否则根据多项式概率接受
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n; cin>>n; a.resize(n);
for(int i = 0; i < n; i++)
cin>>a[i].x>>a[i].y;
srand((unsigned)time(NULL));//随机函数
for(int i = 1; i <= 100; i++)sa();//多跑几遍退火,增加得到最优解的概率
cout<<round(ans)<<"\\n";
return 0;
}
AcWing 2424. 保龄球
/*保龄球规则
+ 一共有n轮,每轮扔2次,每轮10个瓶。每轮得分为2次所扔中瓶子的数量之和。
+ 如果第一次扔满,就没有第二次,同时下一轮总得分翻倍。“全中”
+ 如果两次一共扔满十个球,那么下一轮的第一次得分翻倍。“补中”
+ 如果最后一轮出现“全中”,可再来一轮,但仅限一次。
*/
//题意:给出一场比赛的每轮得分,可以在不改变轮数的情况下重新对各轮的分数排序,求能获得的最大分数。
//思路:直接每次随机枚举两个位置,如果交换后得分增大,就直接交换,如果没有,就以概率决定是否交换。答案取最大值。
#include<bits/stdc++.h>
using namespace std;
int n, m, ans;
struct node{int x, y;}a[55];
int calc(){
int res = 0;
for(int i = 0; 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 sa(){
for(double t=1e4; t>=1e-4; t*=0.99){//模拟降温
int l = rand()%m, r = rand()%m;
int sum1 = calc(); //原先的分数
swap(a[l],a[r]); //随机交换位置
if(n+(a[n-1].x==10)==m){//满足轮数不变的要求
int sum2 = calc(); //交换后的分数
int dt = sum2-sum1; //温度变化
if(dt<0)swap(a[l],a[r]);//没有变的更大就换回来
else if(exp(dt/t)<(double)rand()/RAND_MAX)swap(a[l],a[r]);//按照随机概率换回来
}else{
swap(a[l],a[r]);
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n; m = n;
for(int i = 0; i < n; i++)cin>>a[i].x>>a[i].y;
if(a[n-1].x==10)cin>>a[n].x>>a[n].y, m++;
for(int i = 1; i <= 100; i++)sa();
cout<<ans<<"\\n";
return 0;
}
AcWing 2680. 均分数据
/*统计变量
平均值:所有数加起来除以个数
方差:实际值与平均值之差的平方和除以个数
均方差(即标准差):方差外面套一层根号
*/
//题意:给出n个数,将其分为m组相加,得到一个新的长为m的序列,令该序列的均方差最小,输出最小均方差
//思路:模拟退火,考虑贪心,每组的值越接近,最后的答案就越小,所以每次随机取出一个元素,加到当前最小的分组中,如果答案变小了就保留,否则按照一定概率随机保留。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 30;
int n, m, a[maxn], be[maxn];//记录每个数和所属的组
double avg=0, ans=1e20;
double sqr(double x){return x*x;}
void sa(){
double sum[maxn];//保存每组的值
memset(sum,0,sizeof(sum));
double res = 0;
for(int i = 1; i <= n; i++)be[i]=rand()%m+1, sum[be[i]]+=a[i];//为每个数随机一个组
for(int i = 1; i <= m; i++)res += sqr(sum[i]-avg);//计算初始方差
for(double t=1e4; t>=1e-14; t*=0.99){
double pre = res; //初始方差
int p = min_element(sum+1,sum+m+1)-sum;//找出最小的组
int x = rand()%n+1; //随机一个值
res -= sqr(sum[be[x]]-avg)+sqr(sum[p]-avg);//先从方差中去掉这两组
sum[be[x]]-=a[x]; sum[p] += a[x]; //把随机值放入最小组
res += sqr(sum[be[x]]-avg)+sqr(sum[p]-avg);//计算新的方差
double dt = res-pre;
if(dt<0||exp(dt/t)<(double)rand()/RAND_MAX)be[x] = p;//方差变小就交换,否则随机概率交换
else res = pre, sum[be[x]]+=a[x], sum[p]-=a[x];//不满足就放回去
}
ans = min(ans, res);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i = 1; i <= n; i++)cin>>a[i], avg+=a[i];
avg /= m; //各组的平均值是不变的
srand(time(0)),srand(rand());
for(int i = 1; i <= 1000; i++)sa();
cout<<fixed<<setprecision(2)<<sqrt(ans/m)<<"\\n";//计算标准差
return 0;
}
AcWing 207. 球形空间产生器
/*爬山算法
概述:每次选择临近空间的一组解作为随机解,如果新状态的解更优则修改答案。
与模拟退火的关系:一般都配合模拟退火使用,爬山本身找到的是局部最优解,比如多峰函数找最高峰,局部高峰两边都比他小,此时是最优,但是模拟退火允许他一定概率去找更右边的。
*/
//题意:n维空间中有一个球,给出n+1个球面上点的坐标,求球心坐标。
//思路:std似乎是设球心n维坐标,然后推公式和半径用高斯消元解方程。但是嘛,模拟退火,设完球心坐标之后,可以直接迭代ans和每个点的距离,如果某个点距离大于平均值,就给圆心施加一个向这个点的力,如果近了,就施加一个远离这个点的力。
#include<bits/stdc++.h>
using namespace std;
int n;
struct node{double c[20];}a[20], ans, tmp;
double dis[20]; //存储每个点到ans的距离
double sqr(double x){return x*x;}
double getd(node x, node y){ //获得两点距离
double res = 0;
for(int i = 1; i <= n; i++)
res += sqr(x.c[i]-y.c[i]);
return sqrt(res);
}
void sa(){
for(double t=10005; t >= 0.0001; t*=0.99997){//!!!
double avg = 0;
for(int i = 1; i <= n+1; i++){
dis[i] = getd(ans, a[i]), avg+=dis[i];
}
avg /= n+1; //所有点到ans的平均距离
for(int i = 1; i <= n; i++)tmp.c[i] = 0;
for(int i = 1; i <= n+1; i++){
for(int j = 1; j <= n; j++){
tmp.c[j] += (dis[i]-avg)*(a[i].c[j]-ans.c[j])/avg;
}
}
for(int i = 1; i <= n; i++)
ans.c[i] += tmp.c[i]*t;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i = 1; i <= n+1; i++){
for(int j = 1; j <= n; j++){
cin>>a[i].c[j];
ans.c[j] += a[i].c[j]/(n+1);
}
}
sa();
for(int i = 1; i <= n; i++)
cout<<fixed<<setprecision(3)<<ans.c[i]<<" ";
return 0;
}
以上是关于AcWing进阶算法课Level-4 第六章 搜索 (模拟退火,爬山)的主要内容,如果未能解决你的问题,请参考以下文章