蓝书例题:野餐规划(Python题解)
title: Picnic_Planning(野餐规划)
date: 2024-01-12 14:53:04
tags: [XCPC, 蓝书, 最小生成树]
categories: Algorithm
题目描述
注:你可以在Acwing找到蓝书所有例题的中文体面,该英文题面可以在牛客竞赛OJ尝试提交
The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of themselves into even the smallest vehicle. During the off-season, the brothers like to get together for an Annual Contortionists Meeting at a local park. However, the brothers are not only tight with regard to cramped quarters, but with money as well, so they try to find the way to get everyone to the party which minimizes the number of miles put on everyone’s cars (thus saving gas, wear and tear, etc.). To this end they are willing to cram themselves into as few cars as necessary to minimize the total number of miles put on all their cars together. This often results in many brothers driving to one brother’s house, leaving all but one car there and piling into the remaining one. There is a constraint at the park, however: the parking lot at the picnic site can only hold a limited number of cars, so that must be factored into the overall miserly calculation. Also, due to an entrance fee to the park, once any brother’s car arrives at the park it is there to stay; he will not drop off his passengers and then leave to pick up other brothers. Now for your average circus clan, solving this problem is a challenge, so it is left to you to write a program to solve their milage minimization problem.
输入描述:
Input will consist of one problem instance. The first line will contain a single integer n indicating the number of highway connections between brothers or between brothers and the park. The next n lines will contain one connection per line, of the form name1 name2 dist, where name1 and name2 are either the names of two brothers or the word Park and a brother's name (in either order), and dist is the integer distance between them. These roads will all be 2-way roads, and dist will always be positive.The maximum number of brothers will be 20 and the maximumlength of any name will be 10 characters.Following these n lines will be one final line containing an integer s which specifies the number of cars which can fit in the parking lot of the picnic site. You may assume that there is a path from every brother's house to the park and that a solution exists for each problem instance.
输出描述:
Output should consist of one line of the form
Total miles driven: xxx
where xxx is the total number of miles driven by all the brothers' cars.
示例1
输入
[复制](javascript:void(0)😉
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3
输出
[复制](javascript:void(0)😉
Total miles driven: 183
题目分析:
大致题意是给你一个n个点,m条边的无向图,求出无向图的一课最小生成树,满足1号节点的度数不超过S。两种做法:
1、先求出最小生成树,1号节点度数如果超过S就循环 degree[1] - S 次,每次循环断掉一条与1连接的边,加上一条断掉的连通块与包含1的连通块的边使增加的权重最小。
2、一开始把1号节点先拿掉,那么整个图就分成了诺干个连通块,所以我们只需要对每个连通块求一边MST,再把每个连通块与1连一条权值最小的边。
对第二种方法分类讨论一下
若S<T:
那么我们必然就是无解情况.
若S=T
那么此时我们的生成树,就是我们的最小生成树.
若S>T
此时1的度数小于S,我们还可以通过修改边继续优化答案:枚举1的所有出边,如果(1,x) 边权为 z 还未在MST中,则我们可以找到从 x 到 1 的路径上最长的边(在MST中的边)边权为 w 如果 w - z > 0则我们可以用(1,x)替换这条边使答案优化(w-z)。重复循环(S-degree[1])次或 w-z <= 0.详细实现见代码。
PS: 这道题思路花了十几分钟想出来的,代码调试了几个小时,太锻炼码力了…
python code:
import sys
#初始化
inf = float("inf")
vis, c = [False] * 25, [False] * 25 # c : color
mst = [inf] * 25
s, ans, deg =0, 0, 0 # s, 最小生成树的权值, 统计Park的度数
conn = [0]*25 #connection 记录每个点连的是哪个点
dic = {"Park":1} #字符串映射为整数
g, tree = [[inf]*25 for _ in range(25)], [[inf]*25 for _ in range(25)] #邻接矩阵, 记录树的路径,MST对应的邻接矩阵
ver, p = [0]*25, 0
f, fx, fy = [0]*25, [0]*25, [0]*25 # (fx[i], fy[i]) 是从1 到 i 路径上边权最大的边, 边权为 f[i]
# 输入
n = int(sys.stdin.readline())
cnt = 1
for _ in range(n):
a,b,z = sys.stdin.readline().split()
if a != "Park" and a not in dic:
cnt += 1
dic[a] = cnt
if b != "Park" and b not in dic:
cnt += 1
dic[b] = cnt
g[dic[a]][dic[b]] = min(int(z), g[dic[a]][dic[b]])
g[dic[b]][dic[a]] = min(int(z), g[dic[b]][dic[a]])
s = int(sys.stdin.readline())
#Prim算法
def prim(root):
"""对 ver中的 p个点求 mst"""
global ans, p, deg
mst[root] = 0
for i in range(1, p+1):
x = 0
for j in range(1, p+1):
if not vis[ver[j]] and (x == 0 or mst[ver[x]] > mst[ver[j]]): x = j
vis[ver[x]] = True #找到最小的 mst[ver[x]],加入已选集合
for j in range(1, p+1): #更新生成树的集合
y = ver[j]
if not vis[y] and mst[y] > g[ver[x]][y]:
mst[y], conn[y] = g[ver[x]][y], ver[x]
closest = root
for i in range(1, p+1):
if ver[i] == root: continue
x = ver[i]
ans += mst[x]
tree[conn[x]][x] = tree[x][conn[x]] = mst[x]
if g[1][closest] > g[1][x]: closest = x
deg += 1 # 每个连通块与1连一条边
ans += g[1][closest]
tree[1][closest] = tree[closest][1] = g[1][closest]
def dfs(x): #找连通块
global p
ver[p+1],p = x, p+1
c[x] = True
for y in range(1, cnt+1):
if g[x][y] != inf and not c[y]: dfs(y)
def prim_for_all_comp():
global p
for i in range(2,25):
vis[i], mst[i] = False, inf
# for i in range(25):
# for j in range(25):
# tree[i][j] = inf
c[1] = True
vis[1] = True
for i in range(2,cnt+1):
if not c[i]:
p = 0
dfs(i)
# ver中保存了一个连通块里的点
prim(i) #对这个连通块求prim
def dp(x):
vis[x] = True
for y in range(2, cnt + 1):
if tree[x][y] != inf and not vis[y]:
if f[x] > tree[x][y]:
f[y] = f[x]
fx[y],fy[y] = fx[x], fy[x] #方案不变
else:
f[y] = tree[x][y]
fx[y], fy[y] = x, y
dp(y)
def solve():
"""用于加边"""
global ans
min_val, mini = inf, 0
for i in range(2,cnt+1): #枚举非树边(1,i), 看加哪条边
if tree[1][i] != inf or g[1][i] == inf: continue
# 加入非树边(1,i), 删除数边(fx[i], fy[i])
if g[1][i] - tree[fx[i]][fy[i]] < min_val:
min_val = g[1][i] - tree[fx[i]][fy[i]]
mini = i
if min_val >= 0: return False
ans += min_val
tree[fx[mini]][fy[mini]] = tree[fy[mini]][fx[mini]] = inf
tree[1][mini] = tree[mini][1] = g[1][mini]
#重新计算以mini[i] 为根的子树的状态
f[mini] = g[1][mini]
fx[mini], fy[mini] = 1, mini
for i in range(2,25):
vis[i] = False
dp(mini)
return True
"""主函数"""
# 删除1号点,找出每个连通块,各自求Prim
prim_for_all_comp()
for i in range(2,25):
vis[i] = False
dp(1) #树上动规
while deg < s:
if not solve(): break
deg += 1
print(f'Total miles driven: {ans}')
本文来自博客园,作者:CH-Yu,转载请注明原文链接:https://www.cnblogs.com/chuanhua-blogs/p/18852144