Tyvj 1391 走廊泼水节 - 生成树 - 完全图

本文介绍了一种高效算法,用于计算给定完全图的最小生成树边权总和。通过Kruskal算法结合并查集进行点集合并,并计算新增边的最小权重。

题目大意:
给定一个完全图唯一最小生成树,求这个完全图最小的边权和
完全图:结点两两之间都有边的图

首先我第一回做的时候写了个map骗分的做法。。。我以为N2logN可以过40%数据的,然而被多组数据卡时间了Orz
但!是!map仍然是一个高速骗分利器
所以我先总结下map的用法
map是一类容器,他可以做到哈希表能做的事,其内部由红黑树实现,因此效率十分高。
然后map是拿来做映射的,比如说我想要判断一个坐标(x,y)是否存在过,那我可以map<pair<int, int>, bool> id
第一个参数是索引,第二个参数是实际值,我每次if(id[make_pair(a,b)])
就可以判重
而且他那个id[]里什么都能放,你甚至可以放一个动态数组或者字符串进去。
还有一种我特别喜欢的用法,就是类似哈希的挂链
我们可以map<int, vector<int> > id注意,这里最右边一定要写成> >(带空格),不然编译器会识别为“>>”
想要遍历vector数组,需要建立一个迭代器
vector<int> :: iterator it
遍历则是for(it=id[x].begin(); it<id[x].end(); it++)
.end()返回的是最后一个元素的后一个位置的指针
注意 这里it是指针,需要*it来求值

正解

我们需要高速确定点对之间是否有边以及点对之间新建边的最小权值
可以把输入给的边来一次kruskal的过程,按权值排序,忽视之前的所有边,用并查集连接两个集合。
设想我们有两个点集合,x和y,两个集合之间因为一条边权为w的边要合并了
这里写图片描述
我们把x集合的每个点和y集合的所有点连边,就是一个完全图了,现在需要考虑那些边的边权。
由于我们是按照边权排序的,w此时一定为图中最大边,而题目要求为了最小生成树,也就是说我们新加入的边不能成为最小生成树的一部分,他不能比图中任何边小,这条边的权值又需要为整数,那自然就是w+1,我们此时就增加了(xy1)(w+1)边权

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <map>
const int MAXN = 20010;
using namespace std;
int fa[MAXN],node[MAXN],rk[MAXN];
#define debug(x) cerr << #x << "=" << x << endl;
int tot,last[MAXN],depth[MAXN];
long long total;
long long ans, answ;
int vis[MAXN],n,t;
struct Edge{
    int u,v,w,to;
    Edge(){}
    Edge(int u, int v, int w, int to) : u(u), v(v), w(w), to(to) {}
}e[MAXN*2];
inline void add(int u, int v, int w) {
    e[++tot] = Edge(u,v,w,last[u]);
    last[u] = tot;
}
void init() {
    memset(last,0,sizeof(last));
    tot = 0;
    ans = 0;
    answ = 0;
    total = 0;
    memset(e,0,sizeof(e));
    memset(depth,0,sizeof(depth));
    memset(vis,0,sizeof(vis));
    memset(fa,0,sizeof(fa));
    memset(rk,0,sizeof(rk));
    memset(node,0,sizeof(node));
}
bool cmp(Edge a, Edge b) {
    return a.w < b.w;
}
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool jud(int a, int b) {
    return find(a) == find(b);
}
void merge(int a,int b) {
    node[find(b)] += node[find(a)];
    fa[find(a)] = find(b);

}
int main() {
    scanf("%d",&t);
    while(t--) {
        init();
        scanf("%d", &n);
        for(int i=1; i<n; i++) {
            int u,v,w;
            scanf("%d %d %d",&u,&v,&w);
            total += w;
            add(u,v,w);
            add(v,u,w);
        }
        for(int i=1; i<=n; i++) {
            fa[i] = i;
            rk[i] = 1;
            node[i] = 1;
        }
        sort(e+1,e+tot+1,cmp);
        for(int i=1; i<=tot; i++) {
            int u = e[i].u;
            int v = e[i].v;
            if(!jud(u,v)) {
                ans += (long long)(e[i].w+1)*((long long)(node[find(u)])*(node[find(v)]) - 1);
                merge(u,v);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值