P3959

[NOIP2017 提高组] 宝藏

原来我的题解是用状压 DP 写的,可以戳 这里

然后还咕咕咕咕了一种写法。

然后才知道模拟退火也可以过这题。

可能是因为数据比较小,这个状态的求解范围本身就不是很大,在加上题目数据的强度有限,模拟退火可以很好的过掉这个题。

似乎没有分析复杂度的必要性?

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#define ll long long
using namespace std;

const ll N=20,inf=1e9+7;

const double eps=1e-12,delta=0.996;

ll n,m,ans;

ll s[N+5],dt[N+5],edge[N+5][N+5];

bool vis[N+5];

void init() {
ans=inf;
for(ll i=1;i<=n;i++) s[i]=i;
}

ll f() {
ll res=0;
memset(vis,0,sizeof(vis));
vis[s[1]]=1;dt[s[1]]=1;
for(ll i=2;i<=n;i++) {
ll tmp=inf,v=0;
for(ll j=1;j<=n;j++) {
if(!vis[j]) continue;
if(edge[j][s[i]]!=0&&edge[j][s[i]]*dt[j]<tmp) {
v=j;tmp=edge[j][s[i]]*dt[j];
}
}
if(!v) return inf;
vis[s[i]]=1;res+=tmp;dt[s[i]]=dt[v]+1;
}
return res;
}

void sa() {
double T=5000;
while(T>eps) {
ll x=rand()%n+1,y=rand()%n+1;
swap(s[x],s[y]);
ll now=f();
if(now<ans) ans=now;
else {
if(exp((ans-now)/T)<(double)(rand()/RAND_MAX)) {
swap(s[x],s[y]);
}
}
T*=delta;
}
}

inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();}
return ret*f;
}

inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {
do{buf[++len]=x%10+48;x/=10;}while(x);
}
else {
putchar('-');
do{buf[++len]=-(x%10)+48;x/=10;}while(x);
}
while(len>=0) putchar(buf[len--]);
}

int main() {

srand(19911225);

n=read();m=read();

init();

for(ll i=1;i<=m;i++) {
ll u,v,w;
u=read();v=read();w=read();
if(edge[u][v]==0||edge[u][v]>w)
edge[u][v]=edge[v][u]=w;
}

for(ll i=1;i<=100;i++) {
sa();
}

write(ans);

return 0;
}