[abc复盘] abc319 20230909
总结
- 高光时刻,竟然做出了F,可惜C和E没出,否则上大分。这场过F竟然比G人还少。
- A 打表
- B 模拟,题意瞎写
- C 期望题,全排列
- D 二分答案
- E LCM二维DP
- F 堆贪心+状压枚举
A - Legendary Players
1. 题目描述
2. 思路分析
- 让打印每个人的分,打表。
3. 代码实现
def solve():
s, = RS()
p = {'tourist': 3858,
'ksun48': 3679,
'Benq': 3658,
'Um_nik': 3648,
'apiad': 3638,
'Stonefeang': 3630,
'ecnerwala': 3613,
'mnbvmar': 3555,
'newbiedmy': 3516,
'semiexp': 3481,
}
print(p[s])
B - Measure
链接: B - Measure
1. 题目描述
2. 思路分析
按题意模拟
3. 代码实现
PROBLEM = """给定一个正整数N。打印一个长度为(N+1)的字符串,定义如下。
对于每个i=0,1,2,…,N,
如果存在一个在1和9之间的N的因子j,并且i是N/j的倍数,则si是对应最小的j的数字(si将是1,2,...,9中的一个);
如果不存在这样的j,则si为-。
"""
# ms
def solve():
n, = RI()
ans = []
for i in range(n+1):
for j in range(1,10):
if n%j == 0 and i%(n//j) == 0:
ans.append(str(j))
break
else:
ans.append('-')
print(''.join(ans))
C - False Hope
链接: C - False Hope
1. 题目描述
2. 思路分析
- 以后看到期望考虑一下全集的数量,可行的话其实枚举。
- 9的全排列其实只有36w。
3. 代码实现
PROBLEM = """有一个3×3的方格,每个方格内写着介于1和9之间(包括1和9)的数字。第i行从顶部开始,第j列从左边开始(1≤i≤3,1≤j≤3)的方格包含数字ci,j。
同一个数字可能写在不同的方格中,但不能在垂直、水平或对角线方向上连续出现三个方格。更确切地说,保证ci,j满足以下所有条件。
对于任意1≤i≤3,不满足ci,1=ci,2=ci,3。
对于任意1≤j≤3,不满足c1,j=c2,j=c3,j。
不满足c1,1=c2,2=c3,3。
不满足c3,1=c2,2=c1,3。
高桥将以随机顺序看到每个方格中的数字。当存在一条线(垂直、水平或对角线),满足以下条件时,他会感到失望。
他看到的前两个方格包含相同的数字,但最后一个方格包含不同的数字。
计算高桥在没有失望的情况下看到所有方格中数字的概率。
约束条件
ci,j∈{1,2,3,4,5,6,7,8,9} (1≤i≤3,1≤j≤3)
对于任意1≤i≤3,不满足ci,1=ci,2=ci,3。
对于任意1≤j≤3,不满足c1,j=c2,j=c3,j。
不满足c1,1=c2,2=c3,3。
不满足c3,1=c2,2=c1,3。
"""
"""听木木老师的:枚举9个数的排列 只有362880种,然后这9个数的排列中,不能有前两个数相同的行是按aab顺序排列的
用bad储存所有这种的线:包括三行三列、两条对角线,每条线最多只有两种方法进bad,因此bad最多只有16个元素(case2就是这种数据)
所以,总复杂度是362880*9*16=52254720
"""
# 527 ms
def solve():
g = []
for _ in range(3):
g.append(RILST())
bad = [] # 失望顺序
for x, y, z in (0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6):
for x, y, z in permutations((x, y, z)):
if g[x // 3][x % 3] == g[y // 3][y % 3]:
bad.append((x, y, z))
# print(bad)
# print(len(bad))
p = 0 # 会失望的排列方法
for q in permutations(range(9)):
for x, y, z in bad:
if q.index(x) < q.index(y) < q.index(z): # 不能有这个顺序,有就寄
p += 1
break
print(1 - p / perm(9))
D - Minimum Width
1. 题目描述
2. 思路分析
- 字符串就那么多,显然窗口越宽,高度越小。容易发现是单调的。
- 那么就可以二分。
- 知道宽度的话,模拟求高度是否超过m是可行的
3. 代码实现
PROBLEM = """问题陈述
Takahashi在一个窗口中显示了一个有N个单词的句子。所有单词的高度相同,第i个单词(1≤i≤N)的宽度为Li。
这些单词在窗口中以一个宽度为1的空格分隔开。更具体地说,当句子在宽度为W的窗口中显示时,满足以下条件。
句子被分成几行。
第一个单词显示在顶行的开头。
第i个单词(2≤i≤N)要么在第(i-1)个单词之后有一个宽度为1的间隙,要么显示在包含第(i-1)个单词的行的下一行的开头。它不会在其他地方显示。
每行的宽度不超过W。这里,一行的宽度是指从最左边的单词的左端到最右边的单词的右端的距离。
当Takahashi将句子显示在窗口中时,句子适合M行或更少。找出窗口的最小可能宽度。
"""
def lower_bound(lo: int, hi: int, key):
"""由于3.10才能用key参数,因此自己实现一个。
:param lo: 二分的左边界(闭区间)
:param hi: 二分的右边界(闭区间)
:param key: key(mid)判断当前枚举的mid是否应该划分到右半部分。
:return: 右半部分第一个位置。若不存在True则返回hi+1。
虽然实现是开区间写法,但为了思考简单,接口以[左闭,右闭]方式放出。
"""
lo -= 1 # 开区间(lo,hi)
hi += 1
while lo + 1 < hi: # 区间不为空
mid = (lo + hi) >> 1 # py不担心溢出,实测py自己不会优化除2,手动写右移
if key(mid): # is_right则右边界向里移动,目标区间剩余(lo,mid)
hi = mid
else: # is_left则左边界向里移动,剩余(mid,hi)
lo = mid
return hi
# ms
def solve():
n, m = RI()
a = RILST()
def ok(x):
p = 0
c = 1
for v in a:
if p == 0:
p += v
else:
p += v + 1
if p > x:
c += 1
p = v
if c > m:
return False
return True
print(lower_bound(max(a), sum(a) + n - 1, ok))
E - Bus Stops
链接: E - Bus Stops
1. 题目描述
2. 思路分析
- 由于可以取模,关键的点应该可以容纳所有站台的信息,那么就是所有需求的lcm,其实只有840。
- 这样就可以转移了。
3. 代码实现
# 482 ms
def solve():
n, x, y = RI()
pt = []
ll = 1
for _ in range(n - 1):
pt.append(RILST())
ll = lcm(ll, pt[-1][0])
f = [0] * ll # f[i][j]到0时间模ll为j时,从0到达i的时间
for p, t in pt:
for j in range(ll):
f[j] = f[j] + t + (p - f[j] - j) % p # 到达上一步的实际是时间是f[j]+j,那么要等待(p - f[j] - j) % p
q, = RI()
for _ in range(q):
qi, = RI()
print(qi + x + f[(qi + x) % ll] + y) # 到达0的时间是qi+x
# 1178 ms
def solve1():
n, x, y = RI()
pt = []
ll = 1
for _ in range(n - 1):
pt.append(RILST())
ll = lcm(ll, pt[-1][0])
f = [[0] * ll for _ in range(n)] # f[i][j]到0时间模ll为j时,从0到达i的时间
for i, (p, t) in enumerate(pt, start=1):
for j in range(ll):
f[i][j] = f[i - 1][j] + t + (p - f[i - 1][j] - j) % p
q, = RI()
for _ in range(q):
qi, = RI()
print(qi + x + f[-1][(qi + x) % ll] + y)
F - Fighter Takahashi
1. 题目描述
2. 思路分析
- 第一反应是堆贪心。
- 发现吃药是乘法,一定晚点搞,打不过怪了再吃药。
- 剩下的就是实现,只有10个药,可以暴力枚举组合。
根本不需要用哈夫曼编码
3. 代码实现
PROBLEM = """有一棵具有N个顶点的树。第一个顶点是根节点,第i个顶点(2≤i≤N)的父节点是pi(1≤pi<i)。
每个非根节点上都有一个敌人或药物。高桥想要打败所有的敌人。初始时,他的力量为1,他位于第一个顶点。对于i=2,…,N,第i个顶点的信息由三个整数(ti,si,gi)表示,如下所示。
如果ti=1,表示第i个顶点上有一个敌人。当高桥第一次访问这个顶点时,如果他的力量小于si,高桥被敌人打败并输掉比赛,之后他不能移动到其他顶点。否则,他打败敌人,他的力量增加gi。
如果ti=2,表示第i个顶点上有一种药物。当高桥第一次访问这个顶点时,他服用了这种药物,他的力量乘以gi。(对于一个带有药物的顶点,si=0。)
最多有10个带有药物的顶点。
高桥可以重复移动到相邻的顶点。确定他是否能够打败所有的敌人。
"""
"""由于吃药是乘一个>=1的数,因此一定放在加法后边更好,越滞后越好。
因此优先打怪,用小顶堆优先打弱的怪。打不过了再去吃药。
每次吃药枚举最小的但满足打赢怪的组合。可以直接暴力枚举
"""
# ms
def solve():
n, = RI()
pw = 1 # 初始力量
g = [[] for _ in range(n)]
ee = [(1, 0, 0)] # 根视为100
for i in range(1, n):
p, t, s, gg = RI()
g[p - 1].append(i)
ee.append((t, s, gg))
yao = [] # 现在可用的药
def viagra(pw, t): # 从现在能访问的药里,找到最小的组合,使pw*s>=t,直接状压枚举
if not yao:
return 0
ans = mn = inf # 组合、最小乘积
n = len(yao)
for i in range(1, 1 << n): # 状压
s = 1 # 这个组合的乘积
for j in range(n):
if i >> j & 1:
s *= yao[j]
if s > mn: # 超过当前了不用看了
break
if s * pw >= t and s < mn: # 能打过怪了
mn = s
ans = i
if ans == inf: # 没有能打过怪的组合,直接死
return 0
for j in range(n - 1, -1, -1): # 用掉这些药,逆序
if ans >> j & 1:
yao.pop(j)
return mn
h = [(0, 0)] # 小顶堆,优先搞最弱的怪,如果搞不动,才去吃药,每次吃(最小但满足的组合)
while h:
t, u = heappop(h)
if ee[u][0] == 2:
yao.append(ee[u][2])
if pw < t: # 需要吃药
pw *= viagra(pw, t) # 吃
if pw < t: # 吃完药还是打不过
return print('No')
if ee[u][0] == 1:
pw += ee[u][2]
for v in g[u]:
heappush(h, (ee[v][1], v)) # 如果是药,优先加入药袋,正好它的s是0
print('Yes')
六、参考链接
- 无