XUANMING'S JOURNAL屏幕在深夜微微发亮,我心在远方。
← 回到笔记
FIELD NOTES

题解 P4212 【外太空旅行】

这篇笔记的路线图

模拟退火

退火大法吼啊!

首先不知道模拟退火的请左转这个题

然后回到我们的这个问题,实际上我们的这个题是图论中的一类经典问题——最大团问题,这个问题是指在一个图中找一个子图是完全图,使这个子图的点数最多。这是一个NP完全问题别说你不知道啥是NP完全,一般情况下解决NP完全问题,模拟退火都有较好的表现。

正片开始:

我们先产生一个排列,然后从前往后的枚举点,考虑这个点是否能加入,能就加入,不能就返回答案,这样我们就得到了一个解,然而它不一定使最优解,这个计算函数长这样:

inline int calc(int *a)
{
    for(int i=1;i<=n;++i)
        for(int j=1;j<i;++j)
            if(!e[a[i]][a[j]]) return n-i+1;
    return 0;
}

我们可以每次尝试交换排列中的两个数来退火,就像这样:


void SA()
{
    memcpy(a,b,sizeof(b));
    for(double t=2048;t>1e-18;t*=delta)
    {
        int x=rand()%n+1,y=rand()%n+1;
        if(x==y) continue;
        swap(a[x],a[y]);
        int now=calc(a),D=now-ans;
        if(D<0) ans=now,memcpy(b,a,sizeof(a));
        else if(exp(-D/t)*RAND_MAX<=rand()) swap(a[x],a[y]);
    }
}

多次退火到时间上限:

double pre=clock();
while(clock()-pre<0.95*CLOCKS_PER_SEC) SA();

注意记录一下最优解的排列,每次从那里开始。

然后就没有了。

剩下的就只有最有意思的调参了。

代码,在$O2$的加持一般有九十分,洗一下脸说不定就A了

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <ctime>
using namespace std;
const int maxn=55;
const double delta=0.997;

int a[maxn],e[maxn][maxn],n,ans,b[maxn];

inline int calc(int *a)
{
    for(int i=1;i<=n;++i)
        for(int j=1;j<i;++j)
            if(!e[a[i]][a[j]]) return n-i+1;
    return 0;
}

void SA()
{
    memcpy(a,b,sizeof(b));
    for(double t=2048;t>1e-18;t*=delta)
    {
        int x=rand()%n+1,y=rand()%n+1;
        if(x==y) continue;
        swap(a[x],a[y]);
        int now=calc(a),D=now-ans;
        if(D<0) ans=now,memcpy(b,a,sizeof(a));
        else if(exp(-D/t)*RAND_MAX<=rand()) swap(a[x],a[y]);
    }
}

int main()
{
    srand(time(0));
    int u,v;
    scanf("%d",&n);
    if(n==1) {puts("1");return 0;}
    while(scanf("%d%d",&u,&v)!=EOF)
        e[u][v]=e[v][u]=1;
    for(int i=1;i<=n;++i) b[i]=i;
    ans=calc(b);
    double pre=clock();
    while(clock()-pre<0.95*CLOCKS_PER_SEC) SA();
    printf("%d\n",n-ans);
    return 0,QAQ;
}

输入关键词,搜索全部笔记。