关于我在CQOI Day1 T1里对模拟退火的苦苦挣扎
查看原帖
关于我在CQOI Day1 T1里对模拟退火的苦苦挣扎
205782
R浩轩泽Anmicius楼主2021/4/12 17:49

人品退火诚不欺我

我是在比赛前一周才得知我有机会参加省选的,而在那之前,由于学业繁忙,我已经一个多月没有上机练习了。

本蒟蒻实力与省选比赛水平严重不匹配,但赛场上也能轻松推出T1的m方算法,即首尾端暴力枚举:

void work(int l,int r){
	int minn,maxx;
	minn=inf;maxx=0;
	for(int i=1;i<=l;++i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	for(int i=n;i>=r;--i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	if(l!=n)minn=min(minn,a[l+1]);
	if(r!=1)maxx=max(maxx,a[r-1]);
	ans=min(ans,maxx-minn);
}

main(){
	for(l=1;l<=n;++l)
	if(a[l]>b[l]){
		--l;
		break;
	}
	for(r=n;r>=1;--r)
	if(a[r]<b[r]){
		++r;
		break;
	}
	ans=a[n]-a[1];
	if(!(!l&&r>n)){
		for(int i=1;i<=l;++i)
		for(int j=n;j>=r;--j)work(i,j);
	}
	printf("%d\n",ans);
}

但问题也是显而易见的,这样程序顶天也只有60pts啊喂 虽然这样我也觉得不亏了

在一番激烈的思想斗争之后,我决定试试人品,多花了二十多分钟码了一个退火,于是就有了以下代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e6,inf=0x7fffffff-1;
const double T=1e4;
const double L=0.99;//退火系数 
const double F=1e-13;//末温 
int n,m,a[N],b[N],l,r,ans;
inline void read(int &x){
	x=0;int f=1;char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+(ch^48);
		ch=getchar();
	}
	x*=f;
}
void work(int l,int r){
	int minn,maxx;
	minn=inf;maxx=0;
	for(int i=1;i<=l;++i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	for(int i=n;i>=r;--i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	if(l!=n)minn=min(minn,a[l+1]);
	if(r!=1)maxx=max(maxx,a[r-1]);
	ans=min(ans,maxx-minn);
}
int work_ans(int x,int y){
	if(x<0||x>l||y<r||y>n+1)return inf;
	int minn,maxx;
	minn=inf;maxx=0;
	for(int i=1;i<=x;++i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	for(int i=n;i>=y;--i){
		minn=min(minn,b[i]);
		maxx=max(maxx,b[i]);
	}
	if(x!=n)minn=min(minn,a[x+1]);
	if(y!=1)maxx=max(maxx,a[y-1]);
	return maxx-minn;
}
void SA(){
	double temp=T;
	int x,y,nx,ny,res;
	x=0;y=n+1;
	while(temp>F){
		nx=x+(int)(2*rand()-RAND_MAX)*temp;
		ny=y+(int)(2*rand()-RAND_MAX)*temp;
		res=work_ans(nx,ny);
		if(ans-res>0){
			ans=res;
			x=nx;
			y=ny;
		}
		else if(exp(-(ans-res)/temp)>rand()/RAND_MAX){//(exp(ans-res)*temp>rand()/RAND_MAX) 
			x=nx;
			y=ny;
		}
		temp*=L;
	}
}
int main(){
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
	srand((unsigned int)time(0));srand(rand());
	read(n);read(m);
	for(int i=1;i<=n;++i)read(a[i]);
	for(int i=1;i<=n;++i)read(b[i]);
	for(l=1;l<=n;++l)
	if(a[l]>b[l]){
		--l;
		break;
	}
	for(r=n;r>=1;--r)
	if(a[r]<b[r]){
		++r;
		break;
	}
	ans=a[n]-a[1];
	if(!(!l&&r>n)){
		/*if(n<=1e3)
		for(int i=1;i<=l;++i)
		for(int j=n;j>=r;--j)work(i,j);
		else */
		//以上是O(m^2)算法 
		while(clock()<600)SA();
	}
	printf("%d\n",ans);
	return 0;
}

我没注意到的是,在决定暴枚还是退火的if分支里,我把测试点5~6给排开了,这就意味着把20分的稳定暴力让给了人品退火 (悲)

还有一点比较有意思的是玄学Mertropolis准则公式。套用标准公式:

exp(-(ans-res)/temp)>rand()/RAND_MAX

的话我只能过样例1

但接下来我又意会创造出了一套新公式,即:

(exp(ans-res)*temp>rand()/RAND_MAX)

奇特的是,用上这套公式的话样例1、2稳过,但样例3每次都能得出不同的结果 甚至每次都比最优答案还要小

就结果来说,我应该是白白耗掉了20分暴力,打了一个高级一点的rand()罢了

如果有dalao能对这份代码提供提供意见,我会非常感谢~

2021/4/12 17:49
加载中...