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 。