import sys
input = sys.stdin.readline().strip
# sys.stdin.readline() 读取的数据末尾可能会包含换行符,用strip()去掉
sys.setrecursionlimit(100000)
from functools import lru_cache
@lru_cache(maxsize=None)
def dfs(x):
if x == 0 or x== 1:
return 1
return dfs(x-1)+dfs(x-2)
'''list.sort(key = lambda x:x[1] )
input = sys.stdin.readline().strip
# sys.stdin.readline() 读取的数据末尾可能会包含换行符,用strip()去掉
n = list(map(int,input().split()))
print(' '.join(map(str,a)))
print(objects,sep='',end='\n')
上取整 x/y = (x+y-1)//y math.ceil(x/2) = (x+1)/2
bin(x) oct(x) int(x,y) y:x是几进制数,默认10 hex(x)
#字符串
s.lower() 将字符串中的所有大写字母转换为小写字母。
s.upper() 字符串中的小写字母转为大写字母。
s.isalpha() 检查字符串是否全由字母组成
s.isdigit() 用于检测字符串是否只包含数字字符
#列表 list1.insert(插入位置,插入对象) ; list1.pop(指定位置) ; list1.remove(删除对象) ; list.count(" ") ;
# 字典 dict1.get(a,'x') #有a则输出a的value,否则输出x
'''
''' datetime
import datetime
a=datetime.date(2024,3,4)
b=datetime.date(2024,1,1)
print(a-b)
#输出:63 days, 0:00:00
a.weekday() #返回0-6,0是星期一
from datetime import datetime,timedelta
#这里面常用的就是求两个日期中间隔了多少天
l = datetime(2000,1,1)
r = datetime(2020,10,1)
r - l # 默认的是显示天数,可以通过timedelta进行修改
#以及两个日期中间有多少个星期几什么的;直接遍历
delta = timedelta(days=1)
while l != r:
l += delta
'''
'''heapq
import heapq
a = [11,6,9,8,7,3]
heapq.heapify(a)
print(a) #[3, 6, 9, 8, 7, 11]
heapq.heappush(a,5)
print(a) #[3, 6, 5, 8, 7, 11, 9]
min=heapq.heappop(a)
print(min,a) #3 [5, 6, 9, 8, 7, 11]
heapq.heapreplace(a,4)
print(a) #[4, 6, 9, 8, 7, 11]
'''
'''bisect
from bisect import bisect_left, bisect_right
a = [0, 0, 1, 2, 2, 3]
# bisect_left返回如果将0插入a中,且维持数组顺序不变的条件下
# 放在最左边的索引。说人话就是a中第一个>=target的数的索引
index_1 = bisect_left(a, 0)
# bisect_right说人话就是a中最后一个>=target的数的索引+1
index_2 = bisect_right(a, 0) # 注意返回的是实际的加1!
# 注意,可以添加查找范围,[start,end),左闭右开,不要将a变成a[start:end]引进去!拷贝会浪费大
量的时间!!!
index_1 = bisect_left(a, 0, start,end)
index_2 = bisect_right(a, 0,start, end)
'''
'''
# 二维前缀和 sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]
#区间二维前缀和 sum[x1][y1] - sum[x2][y2] = sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]
# 二维差分 : 差分数组的前缀和=原数组,将前缀和式中sum替换a,a替换成diff
# 双指针: l,r从左右同时开始向中间扫描
'''
'''二分答案
def check(x):
#判断x是否合法,合法返回True,否则返回False
pass
l,r ,ans= #初始化
while l<=r:
mid = (l+r)/2
if check(mid):
ans = mid
l = mid + 1
else:
r = mid - 1
print(ans)
'''
'''位运算 (将数字看作二进制去操作的)
& 与运算:有0为0,全1为1
| 或运算:有1为1,全0为0
^ 异或运算:相同为0,不同为1
~ 取反: 1变成0,0变成1
'''
'''
DFS(u): 1.对u打标记 2.对每个和u相邻的点v:if v未标记:DFS(v)
bfs:from collections import deque
def bfs(s,t): #s: 起点,t: 终点
dis = [-1]*100001
queue = deque()
#1、将起点塞入到队列中,打上标记
queue.append(s)
dis[s] = 0
#2、当队列非空
while len(queue) != 0:
# 2.1 取出队首元素u
u = queue.popleft()
#2.2 判断u是否为终点
if u == t:
return dis[u]
#2.3 将u相连的所有点v,只要v未标记,则入队列
for v in [u-1,u+1,u*2]:
#特判:未越界、未标记
if 0<=v<=100000 and dis[v] == -1:
queue.append(v)
dis[v] = dis[u] + 1
return -1
n,k = map(int,input().split())
print(bfs(n,k))
'''
'''dp
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v) #01
dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v) #完全
for k in range(min(s,j//w)+1):
dp[i][j] = max(dp[i][j],dp[i-1][j-k*w]+k*v) #多重
if j<w: dp[i][j] = max(dp[i][j],dp[i-1][j]) #分组
else: dp[i][j] = max(dp[i][j],dp[i-1][j],dp[i-1][j-w]+v)
'''
01dp背包与魔法 2223
n,m,k = map(int,input().split())
wv = [[0,0]]
for i in range(n):
wi,vi = map(int,input().split())
wv.append([wi,vi])
dp = [[0]*2 for j in range(m+1)]
# dp[i][0] 不使用魔法i容量能装的最大价值,dp[i][1]使用魔法i容量能装的最大价值
for i in range(1,n+1):
for j in range(m,wv[i][0]-1,-1):
dp[j][0] = max(dp[j][0],dp[j-wv[i][0]][0]+wv[i][1])
dp[j][1] = max(dp[j][1],dp[j-wv[i][0]][1]+wv[i][1])
if j>=wv[i][0]+k:
dp[j][1] = max(dp[j][1],dp[j-wv[i][0]-k][0]+2*wv[i][1])
print(max(dp[m][0],dp[m][1]))
01dp购物策略 3965
n = int(input())
a =[[0,0]]
max_t = 0
for i in range(1,n+1):
t,c = map(int,input().split())
t += 1
a.append([t,c])
max_t = max(max_t,t)
max_t += n
dp = [float('inf')] *(max_t+1)
# dp[i] 买i件商品的最少价值。 t看作体积,c看作价值。求最小价值
dp[0] = 0
for i in range(1,n+1):
for j in range(max_t,a[i][0]-1,-1):
dp[j] = min(dp[j],dp[j-a[i][0]]+a[i][1])
print(min(dp[n:]))
树形dp 最大独立集 1319
from collections import defaultdict
n = int(input())
a = [0] + list(map(int,input().split())) #结点权值
edges = defaultdict(list) #结点关系
dp = [[0,a[i]]for i in range(n+1)] #选或不选
for _ in range(n-1): #生成树
u,v = map(int,input().split())
edges[u].append(v)
edges[v].append(u)
def dfs(u,fu):
for v in edges[u]:
if v == fu:
continue
dfs(v,u)
dp[u][0] += max(dp[v][0],dp[v][1])
dp[u][1] += dp[v][0]
dfs(1,0)
print(max(dp[1][0],dp[1][1]))
树形dp最小支配集
from collections import defaultdict
n = int(input())
edges = defaultdict(list)
a = [0] + list(map(int,input().split()))
dp = [[0,a[i]] for i in range(n+1) ]
for _ in range(n-1):
u,v = map(int,input().split())
edges[u].append(v)
edges[v].append(u)
def dfs(u,fu):
for v in edges[u]:
if v == fu:
continue
dfs(v,u)
dp[u][0] += dp[v][1]
dp[u][1] += min(dp[v][0],dp[v][1])
dfs(1,0)
print(min(dp[1][0],dp[1][1]))
树形dp最小覆盖点
他要建立一个古城堡,城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。 注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。 请你编一程序,给定一树,帮 Bob 计算出他需要放置最少的士兵。
from collections import defaultdict
n = int(input())
edges = defaultdict(list)
dp = [[0,1] for i in range(n+1) ]
for _ in range(n-1):
u,v = map(int,input().split())
edges[u].append(v)
edges[v].append(u)
def dfs(u,fu):
for v in edges[u]:
if v == fu:
continue
dfs(v,u)
dp[u][0] += dp[v][1]
dp[u][1] += min(dp[v][0],dp[v][1])
dfs(1,0)
print(min(dp[1][0],dp[1][1]))
换根dp
from collections import defaultdict
def dfs(u, dep, fa): # 传入当前节点u,深度,父节点
global sum_depth
sum_depth += dep # 加上当前节点u的深度
# 遍历u的所有子节点
for v in e[u]:
if v == fa:
continue
dfs(v, dep + 1, u) # 遍历到子节点
siz[u] += siz[v] # 通过这一步,可以统计u所有子节点数
def dfs2(u, fa): # 用来跟新其余节点为根的解
for v in e[u]:
if v == fa:
continue
# dp[v] = dp[u] - siz[v] + (n - siz[v])
dp[v] = dp[u] - 2 * siz[v] + n # 当前节点v的解=左边变化值+右边变化值
dfs2(v, u)
n = int(input())
dp = [0] * (n + 1) # 用来更新不同节点为根时的深度和
siz = [1] * (n + 1) # 以i为根节点,其左子树的节点数,初始值为1表示仅包含该节点
# 建树
e = defaultdict(list)
for _ in range(n - 1):
u, v = map(int, input().split())
e[u].append(v)
e[v].append(u)
# 第一遍dfs,找到以1为根节点的深度和
sum_depth = 0 # 用来记录深度和
dfs(1, 0, 0)
# 第二遍dfs,求出其余点作为根的答案
dp[1] = sum_depth # 6
dfs2(1, 0) # dp=[0, 6, 5, 9, 8, 8]
print(max(dp))