题解:P4809 [CCC 2018] 最大战略储备
YangXiaopei · · 题解
题意
给你
题解
很容易想到题目要求的是所有边的权值和减去这个图的最小生成树的权值和。
考虑如何求最小生成树。
朴素的来看可以使用 kruskal 求解,时间复杂度
然后我们发现,对于一条航线得出的所有边,他们排序后一定在一起,因此,我们直接对于
接着我们发现对于一条横向航线
我们再来看下图。
黑色的正方形表示一个点,红色表示会新造成贡献的边,绿色表示不会造成贡献的边
对于一个行
因此对于横向航线,
纵向航线同理。
因此我们对于
时间复杂度
代码
::::info[码风丑陋但可读性较高的代码]{open}
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m, p, q, sum, cnty, cntx, fa_x[100005], fa_y[100005];
struct node{
int u, v, w, id;//u, v, w表示一条航线,id表示为横向还是纵向,id = 1为横向,id = 2为纵向
bool operator < (const node t) const{
return w < t.w;
}
}x[200005];
//并查集
void init(){
for(int i = 1; i <= m; i++){
fa_x[i] = i;
}
for(int i = 1; i <= n; i++){
fa_y[i] = i;
}
}
int find_x(int x){
return (fa_x[x] == x ? x : fa_x[x] = find_x(fa_x[x]));
}
int find_y(int x){
return (fa_y[x] == x ? x : fa_y[x] = find_y(fa_y[x]));
}
void merge_x(int x, int y){
int u = find_x(x), v = find_x(y);
if(u != v){
cnty--;//合并横向航线造成纵向的连通块减一
fa_x[u] = v;
}
}
void merge_y(int x, int y){
int u = find_y(x), v = find_y(y);
if(u != v){
cntx--;//同上
fa_y[u] = v;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> p >> q;
for(int i = 1; i <= p; i++){
cin >> x[i].u >> x[i].v >> x[i].w;
x[i].id = 1, sum += n * x[i].w;//先加上所有边的权值
}
for(int i = p + 1; i <= p + q; i++){
cin >> x[i].u >> x[i].v >> x[i].w;
x[i].id = 2, sum += m * x[i].w;
}
sort(x + 1, x + p + q + 1);//吧p + q条航线排序
init();//并查集初始化
cntx = n, cnty = m;//初始横向为n个连通块,纵向为m个连通块
for(int i = 1; i <= p + q; i++){
if(x[i].id == 1){//横向航线
if(find_x(x[i].u) == find_x(x[i].v)){//已经联通
continue;
}
sum -= x[i].w * cntx;//减去造成的贡献
merge_x(x[i].u, x[i].v);
}
else{//同上
if(find_y(x[i].u) == find_y(x[i].v)){//
continue;
}
sum -= x[i].w * cnty;
merge_y(x[i].u, x[i].v);
}
}
cout << sum;//输出答案
return 0;
}
::::