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 第六章 搜索 (模拟退火,爬山)的主要内容,如果未能解决你的问题,请参考以下文章

AcWing算法基础课,提高课,进阶课目录

Java牛客项目课_仿牛客网讨论区_第六章

算法提高课——搜索

《算法图解》第六章笔记

《程序是怎样跑起来的》第六章读后感

acwing里的yxc是谁?