刷题记录
本篇内容包括:
DP
图形排版
解题思路:
由于删除一张图片i后,只会对从i+1的图片的行高产生影响,因此此题可拆分成三个part:
①依次删除下标为i的图片,将i+1之后的新的行高,依次加上之前的累计行高的for循环;②新行高的计算。我们可能会碰到多次以不同的图片、状态开始插入,但从某张同样的图片开始新起一行,这新的一行的行高一定是相同的,因此可以用dp状态记录;③而行高的计算依赖于每张图片的加入规则,不再赘述。
- ①的for循环:在累计的h高度下,同时依次计算前面i-1张图片的行高,加上后面i+1张图片的行高,即得到去掉第i张图片的行高,进行比较保存。
- ②:计算后续i+1张图片行高
- 算出从每一张图片新起一行(width=0,height=0),填到最后一张的行高【动态规划(倒序)的for循环,计算1次】:
- 从最后一张N-1填起,填的规则遵守③,dp数组状态。
- 一直填到下一行(满足图片序号< N,且当前width<页面宽度),找到之前可以复用的状态,并记录此行高度h
- 状态转移方程为
- 算出从每一张图片新起一行(width=0,height=0),填到最后一张的行高【动态规划(倒序)的for循环,计算1次】:
1 | import os |
松散子序列
由于每一项的值都大于0,所以我们要尽可能的多选,将问题转换成:给定一个序列,要求不能选取相邻的元素,问能获得的最大价值是多少?
实际上,当前的取舍状态只影响上一项和下一项的取舍状态,于是在遍历到每一项的时候都记录上该项和上一项的取/舍价值即可。则可以说每一个状态(上一步加本一步的取舍操作)都有最佳状态,是贪心的思想。
1、分别记录每项取或不取所形成的价值
2、取的话就等于上一项不取加上本项的价值
3、不取的话等于上一项取或者不取中的最大值
思路和题解来自:线性dp(打家劫舍系列)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27def get(c): #获得字符的价值
return ord(c)-ord('a')+1
s=input()
n=len(s)
dp=[[0,0] for i in range(n)]
dp[0][1]=get(s[0])
for i in range(1,n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]) # 不取,选上一项操作的最大值
dp[i][1]=dp[i-1][0]+get(s[i]) # 取这一项的话上一项只能不取了
print(max(dp[-1][0],dp[-1][1]))
# 这里还可以进一步压缩
# 数组不取,取
# 初始化取第一个
last=[0,getValue(s[0])]
for i in range(1,len(s)):
now=[0,0]
# 计算并更新状态
now[1]=last[0]+getValue(s[i]) # 取的话要要影响上一项,只有一种情况了
now[0]=max(last) # 不取的话,不影响上一项,直接取前面情况的可能最大值
###由于没有逐个存储
### 所以在操纵last变量时一定不要粗心
### 后面的运算不能基于已经更改的last
last=now
print(max(last))
最长回文子串
1 | class Solution: |
对局匹配
1 | import os |
二分查找
管道
参考:第十四届蓝桥杯省赛 Python B 组 D 题——管道(AC)
解题思路:要求水流覆盖完整个管道的最小时间。则在整个时间轴上,都满足条件(检测到),而检测不到,则求时间的操作可以转为一个求最小的二分问题。
- 初始化
lt
为,rt为 - 求中点
mid=math.floor((lt+rt)/2)
是否满足条件,满足则移动rt=mid
,否则移动lt=mid+1
- 持续此操作直至满足
rt==lt
注意lt的更新。我们认为lt代表未判定是否满足的的最小时间(因此每次需要再+1来找到未判定的时间,否则产生死指针),而rt本身及右边是满足的时间。
lt的初值是1,因为我们从 开始判定。而rt的初值是: !(只在Si=开一个两端的)
而对于每个时间,我们需要检测其是否满足条件———是否每个管道都覆盖了水。对于每个需要我们验证的固定的时间,每个在其规定的s时间段打开的阀门的覆盖区间是一定的:[,],这样的无数个线段。于是这样我们就把检验的问题转为一个经典的区间覆盖问题。
区间覆盖问题分为四步:
- 将所有的区间按左端点大小排列
- 判断左端点是否能覆盖到1
- 以左端点最小的其中一个区间的右端点为r,依次比较下一个更小的左端点是否目前的r,满足小于认为覆盖这一片,比较更新r为两者之中最大的右端点,不满足直接结束这一情景的判定
- 最后比较r是否大于整个区间的右端点。
1 | import math |
切片操作
1 | a="-123" |
字符串转整数
1 | import re |
- 正则表达式
- 星号运算符:
- 完全序列解包
- 不完全序列解包 取某些连续的值作为元组
- 字典序列解包**
中心扩散
我真的是很无语,十几天没刷题之后一切都抛之于脑后了,还写的特别慢,还是要天天温习啊。。。
判断回文
1 | class Solution: |
双指针
盛最多的水
本来这道题想直接暴力曲线救国的,结果内存不够……1
2
3
4
5
6
7
8import itertools
class Solution:
def maxArea(self, height: List[int]) -> int:
# 两个要最远,且最高
height_with_id=list(enumerate(height))
matrix=list(itertools.combinations(height_with_id,2))
volum=[abs(line[0][0]-line[1][0])*min(line[0][1],line[1][1]) for line in matrix]
return sorted(volum)[-1]
看了大佬解答,原来是要稍微分析下,从两头开始,每次只移动短板。
- 若向内 移动短板 ,水槽的短板
min(h[i],h[j])min(h[i], h[j])min(h[i],h[j])
可能变大,因此下个水槽的面积 可能增大 。- 若向内 移动长板 ,水槽的短板
min(h[i],h[j])min(h[i], h[j])min(h[i],h[j])
不变或变小,因此下个水槽的面积 一定变小。- 因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。
如此我们就去掉了两两比较时:移动长版一定比 最好状态>当前状态 差的情况。1
2
3
4
5
6
7
8
9
10
11
12class Solution:
def maxArea(self, height: List[int]) -> int:
l,r,res=0,len(height)-1,0
while(l!=r):
if height[l]<height[r]:
res=max(res,height[l]*(r-l))
l+=1
else:
res=max(res,height[r]*(r-l))
r-=1
return res
省赛题目
A:匹配任何含有2 0 2 3三个字
1 | import re |
B:硬币兑换
硬币面值、数量相等且呈等差数列,硬币面值可两两组合,兑换一次一张纸币
求最后能得到的单一面值(纸币加硬币)的某一最大数量
1.暴力解:【我觉得稍微好理解一点】
我们要找出最后每种面值能存在的最大数量【一个面值】的最大数量max。
因此每种硬币最多只能和别的组合一次(或者说在全兑换某个面值以达成目的时和别的组合一次,组合完后剩下的只能丢了),因为组合完多的硬币【面值】和任何面值加起来都不再等于
于是我们就遍历所有可能的硬币组合C(2, 2023)+i,看每种面值的纸币的最大数目,就是这些组合兑换的纸币面值的累积值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import math
# 创建一个结果集表示:可能存在的所有面值,共2023*2种
res = [i for i in range(0,2023+1)]+[0]*2023
# 遍历范围从1到2023
for i in range(1, 2024):
# 再次遍历范围从i+1到2023
for j in range(i, 2024):
# 将'res'列表中索引为(i+j)的元素增加i, 因为兑换的次数取决于最小的硬币数,所以加i
if i==j: res[i + j] +=math.floor(i/2)
else: res[i + j] += i
# 找出'res'列表中的最大值并打印
print(max(res))
# 682425
print(res.index(max(res)))
# 2697
2.归纳推理【大佬好厉害,我老是怕漏最优解】
我们需要使得兑换纸币的数量+硬币本来的数量【这个值我们有】最大。因此想:遍历兑换的纸币面值,寻找最大值。
自然地想到根据面值 2023 的硬币数最多,于是我们先想直接尽可能地兑换面值 2023 的纸币。
这样就会产生两两对半的数列组合:1<>2022、2<>2021……1011<>1021。总数为:。
通过这个例子我们发现,在兑换纸币面值一定的情况下,为了兑换同一种纸币并且使兑换的数量最大,我们用来两两组合的硬币加起来都会等于这个值,所以它是由两 个个首尾相接、数量相等的等差数列组成的。这两个数列组合起来的窗口可以在整个上移动,注意我们的数列最大值只能取2023、最小值只能取1。
但是我们没有必要往小的方向移动。在探讨出{1,……1011}, {1011+1, 1011+2,……2022}的情况后,窗口再向左移动i位,兑换,硬币本来-i。这样就去掉了这些情况!
而往右移动i位,则兑换的数目的等差数列为{2i,……1010+i,1011+i}, {1012+i,……2023},本来为0,兑换纸币为,为一个开口向下的二元函数,也许会在某个位置超过 513589
。1
2
3
4
5
6ans=0
for i in range(0,1011):
coins=(1011+3*i)*(1012-i)/2
ans=max(ans,coins)
print(ans)
# 682425.0
E:保险箱
首先由于右位会影响左位,所以我们只能从右位调起。
以12349->54321作为例子,每一位都有三种情况:
flowchart TD
A[第n位] --> B[进位1235_1]
A[第n位] --> C[退位1233_1]
A[第n位] --> D[不进不退1234_1]
B[进位1235_1] --> E[进位124_21]
B[进位1235_1] --> F[退位122_21]
B[进位1235_1] --> G[不进不退123_49]
C[退位1233_1] --> H[进位124_21]
C[退位1233_1] --> I[退位122_21]
C[退位1233_1] --> J[不进不退123_49]
D[不进不退1234_1] --> i[...]
可以看出,由于每一位i的操作都会诱发前面n-i个的数字变化操作,所以会产生大量重复的节点,但其实每个节点诱发的n-i个数字的变化状态都只有三个(分别由进/不进/不产生)。
因此我们就找到了每一位i在 的三种不同状态下,dp的两个状态量:。注意:对于每个状态只有最小移动步数才是一定的,我们记录每种状态的最小步数下的 。
初始的三个状态的状态量:
简化为
for in :
而状态转移:
首先找到i+1位的数字,三种情况:
分别再计算的三种情况,并对于这三种情况取最小值记录。
遍历
if with state , made totalMove minimal
有问题的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import math
n=int(input())
# 一定要保留前置0
x=int("1"+input())
y=int("1"+input())
# 退位/正常/进位
actions=[-1,0,1]
fhNum=[x//10 + action1 for action1 in actions]
minMove=[abs(x%10-y%10 - action1*10) for action1 in actions]
y=y//10
for i in range(1,n):
thisTonum=y%10
y=y//10
thisMinMove=[math.inf for action in actions]
thisFhNum=[math.inf for action in actions]
for actionj in actions:
nowNum=fhNum[actionj+1]%10
for actioni in actions:
thisMove=abs(nowNum-thisTonum-actioni*10)+minMove[actionj+1]
if thisMinMove[actioni+1]>thisMove:
thisMinMove[actioni+1]=thisMove
thisFhNum[actioni+1]=fhNum[actionj+1]//10 + actioni
minMove=thisMinMove
fhNum=thisFhNum
print(int(min(minMove)))
然后就只有65%通过,剩下的都报:段错误,不知道错在哪。。。我自己生成了100-1000的没报错。。。以后写dp老老实实用数组写了。
下面是大佬的代码(保险箱-lanqiao9003555425的代码 )。。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import os
import sys
n=eval(input())
x=list(map(int,input()))
y=list(map(int,input()))
dp=[[0]*3 for _ in range(n)]
dp[n-1][0]=abs(y[-1]-x[-1])
dp[n-1][1]=10-x[-1]+y[-1] # 进位
dp[n-1][2]=10-y[-1]+x[-1] # 退位
for i in range(n-2,-1,-1):
dp[i][0]=min(dp[i+1][0]+abs(y[i]-x[i]),dp[i+1][1]+abs(y[i]-x[i]-1),dp[i+1][2]+abs(y[i]-x[i]+1))
dp[i][1]=min(dp[i+1][0]+10-x[i]+y[i],dp[i+1][1]+9-x[i]+y[i],dp[i+1][2]+11-x[i]+y[i])
dp[i][2]=min(dp[i+1][0]+10-y[i]+x[i],dp[i+1][1]+11-y[i]+x[i],dp[i+1][2]+9-y[i]+x[i])
print(min(dp[0][0],dp[0][1],dp[0][2]))
比我简短多了,我晕倒了。
寻找整数
一元同余线性方程组的性质:
如果 x^ 为 x 的最小整数解,则通解为 x^+k⋅lcm(m1,m2,…mn) (即相差k倍模数的公倍数)。
则满足前n个条件的解空间为(note: 满足前i+1个条件的解(包括最小解),一定包含于满足i个的解空间中):
- x_fit1=[1,3,5,7,9,11,…]
- x_fit2=[5,11,17,…]
- x_fit3=[5,17,29,…]
1 | from math import gcd |