本以为不过是一道Prim算法模版题,但貌似只能得45分,虽然对我这种蒟蒻来说已经够了。
然而同机房大佬表示可以用模拟退火A了此题,遂习之,终无所获。
然而机缘巧合之下习得了另一种随机算法,于是搭配Prim算法,竟然过了!
首先我们要明确一点:单纯的Prim算法为什么不行。相信聪明的你已经知道了,比如下面就是一个反例:
4 4
1 2 2
1 3 20
2 3 3
3 4 500
Prim算法得出的答案为1508,但是最优解应为507,成功hack。
那么Prim算法是否就真的没有用武之地了呢?非也,我们可以抽出Prim算法的思想——每次取最小。
随机化部分
比如上面那张图,我们按照Prim算法的思想来跑的话,访问顺序如下:
1 => 2 => 3 => 4
但如果访问顺序恰好为:
3 => 4 => 2 => 1
便可以得到最优解507。
基于此思想,我们可以随机访问顺序,然后按照这个访问顺序跑贪心,或许这样不能过,但在大量的随机下,我们十有八九会得出正确的答案,而我们按照一个顺序求出答案仅仅只是n^2的(更何况n最大只有12),所以……
已经没有什么好害怕的了。
代码实现如下:
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (register int i = (a); i <= (b); i++) const long long maxn = 12 + 5, T = 5e3, INF = 1e9; long long n, m, ans = INF; long long a[maxn], dis[maxn], edge[maxn][maxn]; long long read() { long long x = 0, flag = 0; char ch = ' '; while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar(); if (ch == '-') { flag = 1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + ch - '0'; ch = getchar(); } return flag ? -x : x; } void origin() { rep(i, 1, n) rep(j, 1, n) edge[i][j] = ((i == j) ? 0 : INF); rep(i, 1, n) a[i] = i;//最开始的访问顺序就按照1,2,3,4,...,n. } long long check() { memset(dis, 0, sizeof(dis)); long long cost = 0, start = a[1];//取出第一个点. dis[start] = 0; rep(i, 2, n) { int v = a[i]; int cost_v = INF; rep(j, 1, i - 1) { int u = a[j]; if (edge[u][v] * (dis[u] + 1) < cost_v) { cost_v = edge[u][v] * (dis[u] + 1); dis[v] = dis[u] + 1; } } if (cost_v >= INF) return INF; cost += cost_v; } return cost; } void work() { long long tot = T; while (tot--) { long long x = rand() % n + 1, y = rand() % n + 1; swap(a[x], a[y]);//随机交换两个数以达成随机序列. long long new_ans = check(); ans = min(ans, new_ans); } } void write(int x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) write(x / 10); putchar(x % 10 + '0'); } int main() { n = read(), m = read(); origin(); rep(i, 1, m) { long long u, v, w; u = read(), v = read(), w = read(); edge[u][v] = min(edge[u][v], w); edge[v][u] = min(edge[v][u], w); } work(); write(ans); return 0; }