杭电多校8 07
Darnassus
题目链接
题目大意
给定一个长度为\(n\)的序列\(a\),是有\(1-n\)的排列组成,连接\(i j\)两个点的代价为\(abs(i - j) * abs(a[i] - a[j])\),求其最小生成树。
题解
我们发现,若将所有相邻的边连起来,每条边的边权都小于\(n-1\),因此最后的最小生成树边权和一定小于\((n-1)^2\)
于是最小生成树的每条边一定小于\(n-1\)
我们只需要找到所有的这样的边,然后进行kruskal就行
由于边数过多,这里需要采取桶排的思想
代码
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 | #include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 3e7 + 10;
int pre[N];
struct node_ {
int a, b;
};
int head[50004], ne[N];
node_ e[N];
int idx = 0;
inline int find(int x) {
return x == pre[x] ? pre[x] : pre[x] = find(pre[x]);
}
void add(int a, node_ b) {
e[idx] = b;
ne[idx] = head[a];
head[a] = idx ++ ;
}
int p[50004], pos[50004];
int cnt = 0;
int res = 0;
void init(int n) {
for(int i = 1; i <= n; i ++ ) pre[i] = i;
}
void kruskal(int n) {
int res = 0;
int cnt = 0;
for(int i = 1; i <= n - 1; i ++ ) {
for(int j = head[i]; j != -1; j = ne[j]) {
int a = e[j].a;
int b = e[j].b;
int fx = find(a);
int fy = find(b);
if(fx != fy) {
pre[fx] = fy;
res += i;
cnt ++;
}
if (cnt == n - 1) break;
}
if (cnt == n - 1) break;
}
printf("%lld\n", res);
}
int main() {
int t;
scanf("%d", &t);
while(t -- ) {
int n;
memset(head, -1, sizeof(head));
idx = 0;
scanf("%d", &n);
init(n);
for(int i = 1; i <= n; i ++ ) {
int k;
scanf("%d", &p[i]);
pos[p[i]] = i;
}
cnt = 0;
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; i + j <= n; j ++ ) {
if(abs(p[i] - p[i + j]) * j <= n && abs(p[i] - p[i + j]) * j > 0) {
add(j * abs(p[i] - p[i + j]), {i, i + j});
}
if(j * abs(pos[i + j] - pos[i]) <= n && j * abs(pos[i + j] - pos[i]) > 0) {
add(j * abs(pos[i + j] - pos[i]), {pos[i + j], pos[i]});
}
}
}
kruskal(n);
}
}
|