蓝桥杯备赛(九)-复杂DP
概念
DP问题,类似dfs的思想。
实例
Q1
鸣人的影分身(原题链接)
A1
dp[i][j]:总能量为i时,数量为j的组合总数。
dp[i][j]=dp[i][j-1]+dp[i-j][j]。
代码如下:
t=int(input())
for _ in range(t):
m,n=map(int,input().split())
f=[[0 for _ in range(11)] for _ in range(11)]
f[0]=[1 for _ in range(11)]#初始化,当总和为0,个数为i时方案数为1
for i in range(1,m+1):
for j in range(1,n+1):
f[i][j]=f[i][j-1]#有0的情况
if (i-j)>=0:
f[i][j]+=f[i-j][j]#没有的情况,则全部-1就就行
print(f[m][n])
Q2
糖果(原题链接)
A2
很有趣的一道题目。01背包的变种。
代码如下:
n,k=map(int,input().split())
candy_lst=[0]
for _ in range(n):
candy_lst.append(int(input()))
#构建初始dp数组
f=[[0 for _ in range(k)]for _ in range(n+1)]
f[1][(candy_lst[1])%k]=candy_lst[1]#初始化
for i in range(2,n+1):
for j in range(k):
num_1=f[i-1][j]
num_2=f[i-1][(j+k-candy_lst[i])%k]+candy_lst[i] if f[i-1][(j+k-candy_lst[i])%k]!=0 else 0
f[i][j]=max(num_1,num_2)
print(f[n][0])
Q3
密码脱落(原题链接)
A3
区间DP,利用双指针滑动进行求解。6
代码如下:
str_=list(input())
s=len(str_)
str_.extend([0 for _ in range(1001-len(str_))])
f=[[0 for _ in range(1001)]for _ in range(1001)]
for length in range(1,s+1):
for i in range(1,s+1):
#这里需要特判,如果j越界了将无意义
if i+length-1>s:
continue
#为何采用此种遍历方式?避免出现未遍历便利用的情况
j=i+length-1
if i==j:
f[i][j]=1
else:
#此处便看出了使用这种遍历的重要性
if str_[i-1]==str_[j-1]:
f[i][j]=f[i+1][j-1]+2
else:
f[i][j]=max(f[i+1][j],f[i][j-1])
print(s-f[1][s])
Q4
生命之树(原题链接)
A4
树型DP,首先构建树链表,然后采取遍历得到结果。
代码如下:
n=int(input())
happy=[0]
happy.extend(list(map(int,input().split())))
#创建初始字典,每个节点包含的value指的是该节点的连通区域
data={}
f=happy.copy()#记录每个节点联通区域最大值
for _ in range(n-1):
a,b=map(int,input().split())
if a in data:
#若在
data[a].append(b)
else:
data.setdefault(a,[b])
if b in data:
#若在
data[b].append(a)
else:
data.setdefault(b,[a])
#构建成功,递归进行运算每个节点的最大值
def dfs(u,v):
"""
u:表示当前节点(从1开始),v:表示其父节点(父节点无需遍历运算)
"""
contact_lst=data[u]
for i in contact_lst:
if i!=v:
#为子节点
f[u]+=max(0,dfs(i,u))
return f[u]
dfs(1,-1)
print(max(f[1:]))
Q5
斐波那契的前N项和(原题链接)
A5
矩阵快速幂+找规律
代码如下:
n,m=map(int,input().split())
def multi(a,b):
"""
a:代表的是一个矩阵
b:代表的是一个向量
"""
temp=[0 for _ in range(len(b))]
for i in range(len(a)):
for j in range(len(b)):
temp[i]+=a[i][j]*b[j]%m
temp[i]%=m
return temp
def _multi(a,b):
"""
a:代表的是一个矩阵
b:代表的是一个矩阵
"""
temp=[[0 for _ in range(len(a[1]))]for _ in range(len(a))]
for i in range(len(temp)):
for j in range(len(temp[0])):
for c in range(len(a[1])):
temp[i][j]+=a[i][c]*b[c][j]%m
temp[i][j]%=m
return temp
#定义初始矩阵
res=[1,0,0]#才是s1
matrix=[[2,0,-1],[1,0,0],[0,1,0]]
#快速幂算法
n-=1
while n:
if n&1:#是个奇数
res=multi(matrix,res)
matrix=_multi(matrix,matrix)
n>>=1
print(res[0])
Q6
包子凑数(原题链接)
A6
当所有蒸笼的公因数不为1,则说明较大的不为其公因数的倍数的数无法被凑出,此时为无限多
当其公因数为1时,由数据范围可知,99100是小于1e4的,故我们可以dp到10000即可,观察其是否为true
该题可以看作完全背包问题
f[i][j]:当选取第i种包子时,容量为j是否成立(false:不成立,true:成立)
于是f[i][j]=f[i-1][j]|f[i-1][j-Ai]|f[i-1][j-2Ai]|…|f[i-1][j-nAi]…此时需要满足不要越界
此时复杂度为n,这会导致整个复杂度上升至n^3。依据题意,我们可以简化
f[i][j-Ai]=f[i-1][j-Ai]|f[i-1][j-2Ai]|…|f[i-1][j-n*Ai].故原状态转移方程可以简化为
f[i][j]=f[i-1][j]|f[i][j-Ai]
代码如下:
def gcd(a,b):
if b==0:
return a
c=a%b
if c==0:
return b
return gcd(b,c)
n=int(input())
f=[[False for _ in range(10010)]for _ in range(n+1)]
nod_lst=[0]
nod_lst.append(int(input()))
d=nod_lst[-1]
for _ in range(n-1):
nod_lst.append(int(input()))
d=gcd(nod_lst[-1],d)
if d!=1:
print('INF')
else:
f[0][0]=True
for i in range(1,n+1):
for j in range(10010):
f[i][j]=f[i-1][j]
if j>=nod_lst[i]:
f[i][j]|=f[i][j-nod_lst[i]]#注意这里,j=0是可行的方案!!!
res=0
for i in range(1,10010):
if not f[n][i]:
res+=1
print(res)
Q7
括号配对(原题链接)
A7
一道典型的区间DP问题,但是如何求解是个问题。
我们这个题和密码之树那个不太一样,多了一个性质,于是我们必须在状态划分多加考虑两边不同的情况
这题和密码脱落也有些不同,GBE有回文的性质 或者 有另外一种性质,
例如 , ([])均是GBE,因此需要对s[L] 和 s[R]不匹配的情况需要进一步划分,
划分方式和石子合并类似
f[l][r].init(0)
f[l][r]=f[l+1][r-1]+2 if match …1
f[l][r]=max(f[l][k]+f[k+1][r]) k:位于之间 …2
两个式子合并
代码如下:
str_=input()
length=len(str_)
def match(a,b):
if a=='('and b==')':return True
if a=='['and b==']':return True
return False
f=[[0 for _ in range(101)]for _ in range(101)]
for i in range(1,length+1):
for l in range(1,length+1):
if l+i-1<=length:
#这时才有右端点
r=l+i-1
if match(str_[l-1],str_[r-1]):
f[l][r]=f[l+1][r-1]+2
for k in range(l,r):
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r])
print(length-f[1][length])
Q8
旅游规划(原题链接)
A8
树形DP,但需要添加一点直径知识。
代码如下:
def dfs_d(node):
if v[node]:
return
v[node] = 1
for i in g[node]:
if v[i]:
continue
dfs_d(i)
d = d1[i] + 1
if d > d1[node]:
d2[node] = d1[node]
d1[node] = d
p1[node] = i
elif d > d2[node]:
d2[node] = d
global maxd
maxd = max(maxd, d1[node] + d2[node])
def dfs_u(node):
if v[node]:
return
v[node] = 1
for i in g[node]:
if v[i]:
continue
up[i] = up[node] + 1;
if p1[node] == i:
up[i] = max(up[i], d2[node] + 1);
else:
up[i] = max(up[i], d1[node] + 1);
dfs_u(i)
n = int(input())
g = [[] for i in range(n)]
d1 = [0 for i in range(n)]
d2 = [0 for i in range(n)]
up = [0 for i in range(n)]
p1 = [0 for i in range(n)]
v = [0 for i in range(n)]
maxd = 0
for i in range(n-1):
a,b = (int(x) for x in input().split())
g[a].append(b)
g[b].append(a)
v = [0 for i in range(n)]
dfs_d(0)
v = [0 for i in range(n)]
dfs_u(0)
for i in range(n):
tmp = [d1[i],d2[i],up[i]]
tmp.sort()
if tmp[1] + tmp[2] == maxd:
print(i)
总结
难的一批。

本文介绍了多个涉及动态规划的算法问题,包括鸣人的影分身、糖果分配、密码脱落、生命之树、斐波那契数列的前N项和、包子凑数和括号配对等,展示了不同类型的DP应用,如01背包、区间DP和树形DP,并给出了相应的解题代码。同时,提到了矩阵快速幂在解决斐波那契数列问题中的应用。
-复杂DP&spm=1001.2101.3001.5002&articleId=129029918&d=1&t=3&u=0bceacc006634bcb936a278e9c96632a)
2万+

被折叠的 条评论
为什么被折叠?



