图论进阶

Posted landsol

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论进阶相关的知识,希望对你有一定的参考价值。

P1600 「NOIP2016TG」天天爱跑步

将每个玩家的路径拆成两段,分别是起点到 LCA 和 LCA 到终点,所以所有路径都变成了直链,而出发时间不一定为 \\(0\\) 了。

先考虑从上往下走的路径,简单移项可以得到若玩家能被某个观察员观察到当且仅当玩家出发时间减去起点深度等于观察员观察时间减去观察员深度。只与自身有关,用桶记录玩家,在遇到观察员的时候统计即可。

再来考虑从下往上的路径,简单移项可以得到若玩家能被某个观察员观察到当且仅当玩家出发时间加上起点深度等于观察员观察时间加上观察员深度。只与自身有关,用桶记录玩家,在离开观察员的时候统计即可。

P2680 「NOIP2015TG」运输计划

看见最大的最小果断考虑二分答案。

对于超时的每一条路径,我们必须修改其上面的某一条边,利用树上差分可以找出哪些边是经过了超时的每一条路径的。

将花费时间最长的边改造成虫洞显然最优,这样就能完成 check 了。

P3398 仓鼠找 sugar

很经典的问题,判断树上两条路径是否有交点。

如果某个 LCA 在另一条路径上则说明有交点,否则没有。

证明分类讨论下即可,不难。

P4408 「NOI2003」逃学的小孩

结论很一眼,最优解 \\(A,B\\) 一定可以位于某条直径的两个端点,然后暴力枚举 \\(C\\) 计算即可,下面来证明。

首先如果存在多条直径,为了第一段路径尽可能长显然 \\(C\\) 一定也位于某条直径的端点,根据对称性我们只需要考虑任意一条直径的情况即可。

\\(AC,BC,AB\\) 三段路径交点为 \\(D\\),简单画下图可以发现若 \\(CD\\) 不是最短,更换 \\(A,B,C\\) 使得 \\(CD\\) 最短一定不劣。

\\(D\\) 为整棵树的根,那么 \\(A,B,C\\) 必然是根分开的最深的三棵子树中最深的三个点。根据直径的结论 \\(A,B\\) 中必然有一个点为某个直径的端点。

\\(AB\\) 不为直径也就是说 \\(A\\) 所在子树中某个点与 \\(A\\) 形成了直径或是 \\(B\\) 所在子树中某个点与 \\(B\\) 形成了直径,那么将另一个换过来显然更优。

当然 \\(C\\) 可能也要换,不过这不重要了,反正是暴力枚举的。

P1351 「NOIP2014TG」联合权值

距离为 \\(2\\) 无非两种情况,爷爷和孙子或者互为兄弟,枚举父亲,前者找儿子中的最大值,后者找儿子中的最大值和次大值。

P5836 「USACO19DEC」Milk Visits S

将同种奶牛缩成一个点,可以发现仍然是一棵树。只有起点和终点位于同一个连通块并且不是朋友喜欢的种类时朋友才会不高兴。

Python | 蓝桥杯进阶第三卷——动态规划

欢迎交流学习~~


专栏: 蓝桥杯Python组刷题日寄


蓝桥杯进阶系列:

🏆 Python | 蓝桥杯进阶第一卷——字符串
🔎 Python | 蓝桥杯进阶第二卷——贪心
💝 Python | 蓝桥杯进阶第三卷——动态规划
✈️ Python | 蓝桥杯进阶第四卷——图论
🌞 Python | 蓝桥杯进阶第五卷——数论
💎 Python | 蓝桥杯进阶第六卷——搜索

Python|蓝桥杯进阶第三卷——动态规划


🎁 能量项链

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有 N 颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m*r*n(Mars单位),新产生的珠子的头标记为 m, 尾标记为 n
需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=44 颗珠子的头标记与尾标记依次为 (2,3) (3,5) (5,10) (10,2)。我们用记号 表示两颗珠子的聚合操作,(j◎k) 表示第 j,k两颗珠子聚合后所释放的能量。则第 4、1 两颗珠子聚合后释放的能量为:
(4◎1)=10*2*3=60
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为
((4◎1)◎2)◎3)=10*2*3+10*3*5+10*5*10=710

输入描述:
第一行是一个正整数 N(4≤N≤100),表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000。第 i 个数为第 i 颗珠子的头标记(1≤i≤N),当 i<N 时,第 i 颗珠子的尾标记应该等于第 i+1 颗珠子的头标记。第 N 颗珠子的尾标记应该等于第 1 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

输出描述:
只有一行,是一个正整数 E(E ≤ 2.1*10^9),为一个最优聚合顺序所释放的总能量

样例输入:

4
2 3 5 10

样例输出:
710


解题思路

针对动态规划类的题目,我们通常采取以下思路:

  • 确定 dp 数组以及下标的含义
  • 确定递推公式
  • 初始化 dp 数组
  • 确定遍历顺序

接下来的题目我们都会按照这个思路。

确定 dp 数组及其下标的含义:
dp[i][j] 表示第 i 颗珠子到第 j 颗珠子聚合成一颗珠子后所释放的最大能量。

确定递推公式:
dp[i][j] = max(dp[i][k] + dp[k+1][j] + nums[i]*nums[k+1]*nums[j+1]), i ≤ k < j i\\leq k <j ik<j, k k k 为断点

这里的 nums[i] 表示第 i 颗珠子的头标记,但注意,项链为环状,我们可以将链状结构复制一份到后方,同时在末尾再加上第1颗珠子的头标记,比如:样例中的 2, 3, 5, 10,经过处理后是 nums=[2, 3, 5, 10, 2, 3, 5, 10, 2].


注意:从第 k 个位置断开(i ~ k 已经聚合成了一个珠子且 k+1 ~ j 也已经聚合成了一个珠子,这个在状态转移的过程中已经处理好了)左右两个珠子聚合得到的能量为 nums[i]*nums[k+1]*nums[j+1]

初始化 dp 数组:
都初始化为0即可。

确定遍历顺序:
顺序遍历即可。


参考代码

n = int(input())
nums = list(map(int, input().split())) * 2
nums.append(nums[0])
dp = [[0 for j in range(2*n)] for i in range(2*n)]
res = 0

# 递推
# 枚举区间长度
for length in range(1, n):
    # 枚举区间起点
    for i in range(2*n):
        # 计算区间终点
        j = i + length
        # 区间终点超出索引,退出
        if j >= 2*n:
            break
        # 枚举区间断点
        for k in range(i, j):
            # 状态转移
            dp[i][j] = max(dp[i][j], dp[i][k]+dp[k+1][j]+nums[i]*nums[k+1]*nums[j+1])
        # 计算res,最大值
        res = max(res, dp[i][j])

print(res)

🌲 夺宝奇兵

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
在一座山上,有很多很多珠宝,它们散落在山底通往山顶的每条道路上,不同道路上的珠宝的数目也各不相同.下图为一张藏宝地图:

7
3  8
8  1  0
2  7  4  4
4  5  2  6  5

”夺宝奇兵”从山下出发,到达山顶,如何选路才能得到最多的珠宝呢?(每次只能直上或左上)
在上图所示例子中,按照 5-> 7-> 8-> 3-> 7 的顺序,将得到最大值 30.

输入描述:
第一行正整数 N(100 >= N >1),表示山的高度
接下来有 N 行非负整数,第 i 行有 i 个整数 (1 <= i <=N),表示山的第 i 层上从左到右每条路上的珠宝数目。

输出描述:
一个整数,表示从山底到山顶的所能得到的珠宝的最大数目.

样例输入:

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

样例输出:
30


解题思路

确定 dp 数组及其下标的含义:
dp[i][j] 表示位置 i,j 对应的珠宝最大数目。

确定递推公式:
dp[i][j] = max(dp[i-1][j] + cell[i][j], dp[i-1][j-1] + cell[i][j])

初始化 dp 数组:
都初始化为0即可。

确定遍历顺序:
从上往下遍历即可。

参考代码

n = int(input())
# cell 为每个位置的珠宝数目
cell = []
for i in range(n):
    cell.append(list(map(int, input().split())))
# dp[i][j] 表示位置 i,j 对应的珠宝最大数目
dp = [[0 for j in range(n)] for i in range(n)]
dp[0][0] = cell[0][0]
# 从上往下遍历
for i in range(n):
    for j in range(i+1):
        dp[i][j] = max(dp[i-1][j] + cell[i][j], dp[i-1][j-1] + cell[i][j])

# 最后一层的最大值
res = max(dp[-1])
print(res)

🚀 和最大子序列

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
对于一个给定的长度为 N 的整数序列 A,它的“子序列”的定义是:A 中非空的一段连续的元素(整数)。你要完成的任务是,在所有可能的子序列中,找到一个子序列,该子序列中所有元素的和是最大的(跟其他所有子序列相比)。程序要求你输出这个最大值。

输入描述:
输入文件的第一行包含一个整数 N,第二行包含 N 个整数,表示 A
其中
1 < = N < = 100000
-10000 <= A[i] <= 10000

输出描述:
输出仅包含一个整数,表示你算出的答案。

样例输入:

5
3 -2 3 -5 4

样例输出:
4


解题思路

确定 dp 数组及其下标的含义:
dp[i] 表示序列 nums[:i+1] 的子序列的最大和。

确定递推公式:
dp[i] = max(dp[i-1]+nums[i], nums[i])

初始化 dp 数组:
初始化为 nums[0] 即可。

确定遍历顺序:
顺序遍历即可。

参考代码

n = int(input())
nums = list(map(int, input().split()))
print(nums[:1])
# dp[i] 表示序列 nums[:i+1] 的子序列的最大和
dp = [nums[0] for i in range(n)]
for i in range(1, len(nums)):
    dp[i] = max(dp[i-1]+nums[i], nums[i])
print(max(dp))

💡 超级玛丽

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
大家都知道" 超级玛丽" 是一个很善于跳跃的探险家,他的拿手好戏是跳跃,但它一次只能向前跳一步或两步。有一次,他要经过一条长为 n 的羊肠小道,小道中有 m 个陷阱,这些陷阱都位于整数位置,分别是 a 1 , a 2 , . . . . , a m a1,a2,...., am a1,a2,....,am,陷入其中则必死无疑。显然,如果有两个挨着的陷阱,则玛丽是无论如何也跳过不去的。
现在给出小道的长度 n,陷阱的个数及位置。求出玛丽从位置 1 开始,有多少种跳跃方法能到达胜利的彼岸(到达位置 n)。

输入描述:
第一行为两个整数 n,m
第二行为 m 个整数,表示陷阱的位置

数据规模和约定
40 >= n >= 3, m >= 1
n > m;
陷阱不会位于 1n

输出描述:
一个整数。表示玛丽跳到 n 的方案数

样例输入:

4 1
2

样例输出:
1


解题思路

确定 dp 数组及其下标的含义:
dp[i] 表示到达位置 i 的跳跃方法数。

确定递推公式:
当满足:index.count(i) == 0 即此处没有陷阱, dp[i] = dp[i-j] + dp[i]

初始化 dp 数组:
初始化为 0 即可。

确定遍历顺序:
顺序遍历即可。


参考代码

n, m = map(int, input().split())
index = list(map(int, input().split()))
# dp[i] 表示到达位置 i 的跳跃方法数
# dp[0] = 0 便于后续处理
dp = [0 for i in range(n+1)]
dp[1] = 1
for i in range(2, n+1):
    for j in [1, 2]:
        # 判断前两个位置是否有陷阱
        if index.count(i) == 0:
            dp[i] = dp[i-j] + dp[i]
print(dp[-1])

🍞 2^k进制数

题目:
时间限制:
1s

内存限制:
128MB

题目描述:
r r r 是个 2 k 2^k 2k 进制数,并满足以下条件:

  • r r r 至少是个 2 2 2 位的 2 k 2^k 2k 进制数。
  • 作为 2 k 2^k 2k 进制数,除最后一位外, r r r 的每一位严格小于它右边相邻的那一位。
  • r r r 转换为 2 2 2 进制数 q q q 后,则 q q q 的总位数不超过 w w w

在这里,正整数 k ( 1 ≤ k ≤ 9 ) k(1≤k≤9) k(1k9) w ( k < w ≤ 30000 ) w(k<w≤30000) w(k<w30000) 是事先给定的。

问:满足上述条件的不同的 r r r 共有多少个?
我们再从另一角度作些解释:设 S S S 是长度为 w w w 的 01字符串(即字符串 S S S w w w01组成), S S S 对应于上述条件中的 q q q。将 S S S 从右起划分为若干个长度为 k k k 的段,每段对应一位 2 k 2^k 2k 进制的数,如果 S S S 至少可分成 2 2 2 段,则 S S S 所对应的二进制数又可以转换为上述的 2 k 2^k 2k 进制数 r r r

例:设 k = 3 , w = 7 k=3,w=7 k=3w=7。则 r r r 是个八进制数 ( 2 3 = 8 ) (2^3=8) (23=8)。由于 w = 7 w=7 w=7,长度为 7 的 01 字符串按 3位一段分,可分为 3 段(即1,3,3,左边第一段只有一个二进制位),则满足条件的八进制数有:

2位数:
高位为16 个(即 12,13,14,15,16,17 ),高位为 25 个,…,高位为61个(即67 )。共 6+5+…+1=21 个。

3位数:
高位只能是1,第 2 位为 25 个(即123,124,125,126,127),第2位为34个,…,第 2 位为 61个(即 167)。共5+4+…+1=15个。
所以,满足要求的 r r r 共有 36个。

输入描述:
只有1行,为两个正整数,用一个空格隔开:
k k k w w w

输出描述:
1行,是一个正整数,为所求的计算结果,即满足条件的不同的 r r r 的个数(用十进制数表示),要求最高位不得为0,各数字之间不得插入数字以外的其他字符(例如空格、换行符、逗号等)。
(提示:作为结果的正整数可能很大,但不会超过200位)

样例输入:
3 7
样例输出:
36


解题思路

确定 dp 数组及其下标的含义:
dp[i][j] 表示有 (i+1) 位数字,最高位数字是 j 时满足条件的数字个数。

确定递推公式:
j!=0dp[i][j] = dp[i-1][j+1]+dp[i-1][j+2]+dp[i-1][j+3]······
也就是说最高位不是0的时候,满足条件的数字个数是,第二高位的数字大于最高位的数字的情况的和。
j==0dp[i][j] = dp[i-1][j]+dp[i-1][j+1]+dp[i-1][j+2]+dp[i-1][j+3]······
如果最高位是 0,那么第二高位数字则可以取0。也就比上一个情况多加了个 dp[i-1][j]

初始化 dp 数组:
dp[0][j]=1 即只有一位数字时只有一种情况

确定遍历顺序:
顺序遍历即可。

此外注意最终结果的计算和处理,具体见代码。


参考代码

import math
k, w = map(int, input().split())
# rows, cols 为dp数组的行数和列数
rows, cols = w//k+1, 2**k
# i 是 2^k 进制下数字的位数
# j 是 每一位上可以取到的值
# dp[i][j] 表示有 (i+1) 位数字,最高位数字是 j 时满足条件的数字个数
dp = [[0 for j in range(cols)] for i in range(rows)]
# 初始化 res,为结果
res = 0

# dp 数组初始化
# dp[0][j] 表示有 1 位数字的情况
for j in range(cols):
    dp[0][j] = 1

# 递推
for i in range(1, rows):
    for j in range(cols):
        dp[i][j] = sum(dp[i-1][j+1:])
        if j == 0:
            dp[i][j] += dp[i-1][j]

# 计算最后的结果
if w%k == 0:
    # 此时最高位可以取 0 到 cols, 即最后一行所有情况都可以用
    res = sum(dp[-1])
else:
    # 此时最高位只能取 0 到 2**(w%k)-1
    res = sum(dp[-1][:2**(w%k)])

# 题目中要求位数 &g

以上是关于图论进阶的主要内容,如果未能解决你的问题,请参考以下文章

随笔分类

图论1-k短路

Python | 蓝桥杯进阶第三卷——动态规划

实验13读后感:《算法竞赛进阶指南》

网络流初步详解

暑期任务安排