solution-p4212

题解 P4212 【外太空旅行】

原题

前置芝士:模拟退火

题意:给你一个无向图,求出这个图的最大完全子图的大小。(即最大团问题)

众所周知,最大团问题是 \(\texttt{NPC}\) 问题。观察数据范围 \(1 \le n \le 50\),我们可以用模拟退火完成此题。

一般的模拟退火题都是维护一个序列,每次随机交换其中两个检验最优方案的方法来实现。

这题也可以采用类似的方法:我们把 \(n\) 个点放到一个序列中,每次随机交换两个点,\(\mathcal O(n^2)\) 枚举 \(k\),直到前 \(k\) 个点不组成一个团,\(k - 1\) 即为当前答案。

大概长这个样:

int query()
{
	for(int i = 1;i <= n;i++)
		for(int j = i - 1;j >= 1;j--)
			if( !edge[ a[i] ][ a[j] ] )
				return i - 1;
	return n;
}

其中 a[i] 表示序列的第 \(i\) 个点。

当然由于这题正解不是退火,所以参数比较难调,卡时也要比较极限。我的参数仅供参考。

代码

const double eps = 1e-8;
const double delta = 0.999;
int ans = -inf;
int n;
bool edge[N][N]; // 邻接矩阵存图,方便判断两点连通 
int a[N],ansa[N]; // 当前序列,最优序列 
int query()
{
	for(int i = 1;i <= n;i++)
		for(int j = i - 1;j >= 1;j--)
			if( !edge[ a[i] ][ a[j] ] )
				return i - 1;
	return n;
}
void SA()
{
	double T = 5000; // 初温调高一点 
	for(int i = 1;i <= n;i++)
		a[i] = ansa[i];
	while(T > eps)
	{
     	int x = rand() % n + 1;
     	int y = rand() % n + 1;
     	swap( a[x] , a[y] ); // 随机交换序列的两个数 
		int cur = query(); 
		if(cur > ans)
		{
			ans = cur;
			for(int i = 1;i <= n;i++)
				ansa[i] = a[i];
		}
		else if( exp( (cur - ans) / T ) * RAND_MAX < rand() )
			swap( a[x] , a[y] );
		T *= delta;
	}
}
int main()
{
	srand( time(NULL) );
	cin >> n;
	int u,v;
	while(cin >> u >> v)
		edge[u][v] = edge[v][u] = true;
	for(int i = 1;i <= n;i++)
		ansa[i] = i;
	while(clock() < CLOCKS_PER_SEC * 0.99) // 卡时 
		SA();
	cout << ans << endl;
    return 0;
}

不保证一直 AC 。

posted @ 2024-03-06 16:55  iorit  阅读(12)  评论(0)    收藏  举报