P1194 买礼物(黄色 普及/提高-)
MST, https://www.luogu.com.cn/problem/P1194
又到了一年一度的明明生日了,明明想要买
但是,商店老板说最近有促销活动,也就是:
如果你买了第
现在明明想知道,他最少要花多少钱。
输入格式
第一行两个整数,
接下来
我们保证
特别的,如果
注意
输出格式
一个整数,为最小要花的钱数。
输入输出样例 #1
输入 #1
1 1
0输出 #1
1输入输出样例 #2
输入 #2
3 3
0 2 4
2 0 2
4 2 0输出 #2
7说明/提示
样例解释
先买第
(同时满足多个“优惠”的时候,聪明的明明当然不会选择用
数据规模
对于
对于
2018.7.25新添数据一组
这是一道典型的最小生成树(Minimum Spanning Tree, MST)问题。
算法分析
我们可以将购买每件物品的过程抽象为一个图:
- 虚拟节点:引入一个虚拟源点
0,代表“直接购买”。从节点0向每个物品节点( )连一条权重为 的边,表示以原价 直接购买物品 。 - 物品间优惠:对于任意两件物品
和 ,如果存在优惠价格 且 ,则在节点 和 之间连一条权重为 的无向边。 - 求解:由于我们需要买齐所有
样物品,并且希望总花费最小。这相当于在包含虚拟源点 0和所有物品节点组成的图(共个节点)中,求一棵包含所有节点的最小生成树。最小生成树的边数刚好为 ,其权值之和即为最少花费。
我们可以使用 Kruskal 算法 来解决此问题:
- 将所有边按权重从小到大排序。
- 使用并查集(Union-Find)维护连通性。
- 依次选择权重最小且不构成环的边,直到选择了
条边。
Python 3 实现代码
python
import sys
def solve():
# 读取所有输入数据
input_data = sys.stdin.read().split()
if not input_data:
return
A = int(input_data[0])
B = int(input_data[1])
edges = []
# 1. 建立虚拟源点 0 到所有物品的边,权重为原价 A
for i in range(1, B + 1):
edges.append((A, 0, i))
# 2. 读取优惠矩阵,建立物品之间的边
idx = 2
for i in range(1, B + 1):
for j in range(1, B + 1):
k = int(input_data[idx])
idx += 1
# 只考虑有优惠(k > 0)且避免重复添加无向边(i < j)
if i < j and k > 0:
edges.append((k, i, j))
# 将所有边按权值从小到大排序
edges.sort()
# 并查集初始化
parent = list(range(B + 1))
def find(x):
path = []
while parent[x] != x:
path.append(x)
x = parent[x]
for node in path:
parent[node] = x
return x
def union(x, y):
root_x = find(x)
root_y = find(y)
if root_x != root_y:
parent[root_x] = root_y
return True
return False
# Kruskal 算法求最小生成树
total_cost = 0
edges_count = 0
for w, u, v in edges:
if union(u, v):
total_cost += w
edges_count += 1
# 当构建了 B 条边时,说明 B+1 个节点已全部连通
if edges_count == B:
break
print(total_cost)
if __name__ == '__main__':
solve()复杂度分析
时间复杂度:
- 构建边集的时间复杂度为
。 - 排序边集的时间复杂度为
,其中边数 约为 。 - 并查集操作接近
。 - 整体时间复杂度为
,在 的数据规模下,可以高效运行完毕。
- 构建边集的时间复杂度为
空间复杂度:
- 存储边集需要
的空间,并查集需要 的空间,整体空间复杂度为 ,内存占用极小。
- 存储边集需要