Logo zibenlun 的博客

博客

美妙的模拟退火

...
zibenlun
2025-12-01 12:58:16
分块好啊

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-08-28 20:06:43

骗分还得是模拟退火

思路

了解了模拟退火算法后,本题几乎成为了一道板子题,但是还有一些细节需要去解决一下。

模拟退火算法本来是用来求最值的,所以对于本题来说其中退火的环节确实有点多余,所以我们为了本人更好地偷懒解法的正确性,我们要舍去多余的退火。

舍去了退火之后会发现,如何解决是否交换的问题呢?其实只需要全部交换就可以了,因为异或这种运算没什么太大的规律。

之后是我们总体的思路,首先预处理一下要用的 $lg$ 数组,然后对于每一个 $n$ 准备好的原数组和它的前 $m$ 个异或和,还有要比较的数。之后跑模拟退火直到出结果或者无解就好了。

更多的代码细节还是直接看代码吧。

Code

#include<bits\/stdc++.h>
using namespace std;
inline long long read()
{
	long long s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') {
		s=(s<<3)+(s<<1)+(ch^48);
		ch=getchar();
	}
	return s;
}
inline void write(long long x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x\/10);
	putchar(x%10+'0');
}
int flag=0,lg[1000005];
int a[1000005],n,m,t,pos=1,sum;
void SA(){
	for(int i=1;i<=10000;i++){
		int l=rand()%m+1,r=m+rand()%(n-m)+1;
		sum^=a[l];
		sum^=a[r];
		swap(a[l],a[r]);
		if(sum==pos){
			flag=1;
			return ;
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	srand(time(0));
	cin>>t;
	for(int i=1;i<=1000000;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	while(t--){
		cin>>n>>m;
		if(n==m){
			for(int i=1;i<=n;i++) a[i]=i;
			flag=sum=0;
			for(int i=1;i<=m;i++) sum^=i;
			pos=1;
			for(int i=1;i<=lg[n];i++) pos*=2;
			pos--;
			if(sum==pos) {
				for(int i=1;i<=n;i++) cout<<i<<" ";
				cout<<"\n";
				continue;
			}
			else {
				cout<<"-1\n";
				continue;
			}
		}
		for(int i=1;i<=n;i++) a[i]=i;
		flag=sum=0;
		for(int i=1;i<=m;i++) sum^=i;
		pos=1;
		for(int i=1;i<=lg[n];i++) pos*=2;
		pos--;
		for(int i=1;i<=90;i++){
			SA();
			if(flag){
				for(int j=1;j<=m;j++){
					cout<<a[j]<<" ";
				}
				cout<<"\n";
				break;
			}
		}
		if(!flag){
			cout<<"-1\n";
		}			
	}
	return 0;
}

温馨提示

本代码不一定可以一遍过,如果答案错误的话,多提交几遍就可以了。

评论

KSCD_
退火螳臂

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。