果真是宝藏题目。
0x01 前置芝士
这道题我是真没往状压dp上去想。题目来源。
大概看了一下结构。盲猜直接模拟退火!xyx
所需知识点:模拟退火,贪心。
0x02 分析
题目大意:给你一个图,可能有重边,可能有环。让你在这个图上构出一棵树,使得其权值和最小,每条边的权值定义为:这条边的长度 ( imes) 这条边的两个端点中深度小的那一个的深度。输出这个最小权值和。
于是我们尝试去构造一个序列 (a),然后按照这个序列去构树。
按照这个序列构出的树保证第 (a[i]) 个结点一定与第 (a[j], j in [1, i - 1]) 个结点相连。
接下来我们贪心考虑。我们需要使每个点都被拓展到,且权值最小,又因为序列规定,我们需要在已经拓展到的结点去拓展当前结点,那么一定选到当前结点权值最小的已被拓展过的结点进行拓展最优。
即,如果该树满足 (len(a[i], a[j]) imes dep(a[j]) = mathrm{Min}{len(a[i], a[k]) imes dep(a[k]), k in [1, i - 1]}),其中 (len(x, y)) 表示结点 (x) 到 (y) 边的长度,(dep(x)) 表示结点 (x) 的深度。则此时我们按照这个方式构出的树一定为当前序列下权值和最小的树。
于是题目转换为找到使得构成的树权值和最小的序列,并得到这个序列对应的最小权值和。
这样就是裸的模拟退火了。我们以序列 (A,A_i = i) 为初始序列,不断扰动,找到最小值。
调一下参数,可过。
srand: 998244353,SA: 7,delta of temperature: 0.996,initial temperature: 1e4,Target temperature: 0.1
0x03 具体实现
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef long long LL;
int Max(int x, int y) {return x > y ? x : y;}
int Min(int x, int y) {return x < y ? x : y;}
int Abs(int x) {return x < 0 ? -x : x;}
void Swap(int &x, int &y) {int t = x; x = y; y = t;}
int read() {
int k = 1, x = 0;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
x = (x << 3) + (x << 1) + s - '0';
s = getchar();
}
return x * k;
}
void write(int x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
void print(int x, char s) {
write(x);
putchar(s);
}
const int MAXN = 15;
const int INF = 0x3f3f3f3f;
const double q = 0.996;
int mp[MAXN][MAXN], n;
int a[MAXN], new_a[MAXN], dep[MAXN];
int f() {
dep[new_a[1]] = 1;
int res = 0;
for(int i = 2; i <= n; i++) {
int tmp = INF;
for(int j = 1; j < i; j++)
if(mp[new_a[j]][new_a[i]] != INF && mp[new_a[j]][new_a[i]] * dep[new_a[j]] < tmp) {
tmp = mp[new_a[j]][new_a[i]] * dep[new_a[j]];
dep[new_a[i]] = dep[new_a[j]] + 1;
}
if(tmp == INF)
return INF;
res += tmp;
}
return res;
}
void Accept(int now, int &ans) {
for(int i = 1; i <= n; i++)
a[i] = new_a[i];
ans = now;
}
int SA() {
for(int i = 1; i <= n; i++)
a[i] = i;
int ans = 0x7f7f7f7f;
double t = 1e4;
while(t > 0.1) {
for(int i = 1; i <= n; i++)
new_a[i] = a[i];
Swap(new_a[rand() % n + 1], new_a[rand() % n + 1]);
int now = f(), delta = now - ans;
if(delta < 0)
Accept(now, ans);
else if(exp(-delta / t) * RAND_MAX >= rand())
Accept(now, ans);
t *= q;
}
return ans;
}
int main() {
srand(998244353);
memset(mp, 0x3f, sizeof mp);
n = read();
int m = read();
for(int i = 1; i <= m; i++) {
int u = read(), v = read(), w = read();
mp[u][v] = Min(mp[u][v], w);
mp[v][u] = Min(mp[v][u], w);
}
int ans = INF;
for(int i = 1; i <= 7; i++)
ans = Min(ans, SA());
print(ans, '
');
return 0;
}