[atc复盘] abc296 20230401

一、本场总结

  • 第一次打atcoder,abc做到F。
  • A 模拟。
  • B 下标计算。
  • C 哈希表。
  • D 数学贪心枚举。
  • E 看似是博弈其实是拓扑排序。
  • F 模拟/逆序对。
  • 在这里插入图片描述
  • 在这里插入图片描述
    在这里插入图片描述

二、 A - Alternately

链接: A - Alternately

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 直接枚举每个相邻对是否不同即可。

3. 代码实现

# Problem: A - Alternately
# Contest: AtCoder - AtCoder Beginner Contest 296
# URL: https://atcoder.jp/contests/abc296/tasks/abc296_a
# Memory Limit: 1024 MB
# Time Limit: 2000 ms

import sys
import bisect
import random
import io, os
from bisect import *
from collections import *
from contextlib import redirect_stdout
from itertools import *
from array import *
from functools import lru_cache
from types import GeneratorType
from heapq import *
from math import sqrt, gcd, inf

if sys.version >= '3.8':  # ACW没有comb
    from math import comb

RI = lambda: map(int, sys.stdin.buffer.readline().split())
RS = lambda: map(bytes.decode, sys.stdin.buffer.readline().strip().split())
RILST = lambda: list(RI())
DEBUG = lambda *x: sys.stderr.write(f'{str(x)}\n')

MOD = 10 ** 9 + 7
PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_a
给你整数n和长为n的字符串s
s由M/F两种字母组成代表男女。
检测s是否是男女交替的。
"""


def solve():
    n, = RI()
    s, = RS()
    for i in range(n - 1):
        if s[i] == s[i + 1]:
            return print('No')
    print('Yes')

三、B - Chessboard

链接: B - Chessboard

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 枚举找下标,转换一下下标即可。

3. 代码实现

PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_b
给你一个8*8的棋盘。由'.*'两种字符组成。
棋盘上只有一个*,找出*的坐标。
其中横坐标从下往上编码为1-8;纵坐标从左到右编码为a-h。
"""


#       ms
def solve():
    g = []
    for _ in range(8):
        s, = RS()
        g.append(s)
    for i in range(8):
        for j, c in enumerate(g[i]):
            if c == '*':
                a = chr(ord('a') + j)
                print(f"{a}{8 - i}")

四、C - Gap Existence

链接: C - Gap Existence

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 直接把a转成set。
  • 枚举每个数,检查和它差为d的数据是否在set里。

3. 代码实现

PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_c
给你整数N(2<=N<=2e5),X(-1e9<=X<=1e9),和长为N的数组A。
请问A中是否存在两个数差值为X。这两个数可以是同一个数。
"""


#       ms
def solve():
    n, x = RI()
    a = set(RILST())
    for v in a:
        if v - x in a or v + x in a:
            return print('Yes')
    print('No')

五、D - M<=ab

链接: D - M<=ab

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 由于数据范围大,直接枚举肯定不行。可以先考虑无解的情况:
  • 如果n*n<m肯定无解,因为即使a、b都取到n,乘积也够不到m。
  • 如果n*n=m,那ab只能都取到n,任意数再小都不够m,则乘积只能是m。
  • 若m<=n,显然ab可以是1 m,最小就是m,返回m即可。
  • 其它情况(n*n>m&&n<m)则需要遍历尝试:
    • 首先初始化ans上界,起码ab可以取到n*n,这个数是满足条件的答案。我们枚举找比它更小的答案。
    • 枚举乘积答案肯定是不行的,考虑枚举因子a/b里小的那个(因为乘法满足交换律,枚举哪个都可以),设为a。
    • a显然不会超过sqrt(m),这时可以计算b=ceil(m/a)。上取整后就可以保证a*b>=m。
    • 因此这么枚举,只需要保证ab<=n即可更新答案。

  • 补充一下为什么不需要枚举>sqrt(m)的部分:
    • 如果a>sqrt(m),那么当b=sqrt(m)则乘积一定超过m,为了取到最小的b=ceil(m/a),b只会更小,那这个b一定在之前枚举过了(作为a枚举),或者说这个数对已经被枚举过了。

3. 代码实现

PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_d
给你两个正整数n,m(1<=N,M<=1e12)。
找到一个最小的x,使x满足:
1.x>=m
2.x可以被分解为两个数ab的乘积,其中1<=a,b<=n
"""


#       ms
def solve():
    n, m = RI()
    if n * n < m:
        return print(-1)
    if n * n == m:
        return print(m)
    if m <= n:
        return print(m)
    s = int(m ** 0.5)
    ans = n * n
    for a in range(2, min(s, n) + 1):
        b = (m + a - 1) // a
        if b <= n:
            ans = min(ans, b * a)
    print(ans)

六、E - Transition Game

链接: E - Transition Game

1. 题目描述

在这里插入图片描述

2. 思路分析

这题读题没读懂,就去做F了,赛后补的E。主要是没看懂那个x是哪来的,后来知道写在黑板上的数就是x。而且黑板上每次只有一个数。
  • 手玩一下发现:替换操作就是把数字沿着值作为下标->这个路径往后走,路径可以用图考虑。
  • 把整个数组按这个操作建图,图中会存在环,n个节点n条边;而且这个图是内向的,因为出度最多是1.即进了环就出不来。
  • 由于第i轮操作的目标数字是知道的,即i,B的目标是让路径的终点是i,那么若i在环上,B可以调整起始点的位置,使ki步后正好到达i。(因为在环上代表前边步数可以无限)
  • A的目标是让i不在终点,那么对于所有没进环上的i,可以设置ki>=n足够大,使终点进环。
  • 因此对于所有在环上的i,B必赢;不在环上的点,A必赢。
  • 找所有环上的点数量不好弄;反过来求不在环上的点,用拓扑排序即可。

3. 代码实现

PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_e
给你N和长度为N的数组a,其中1<=a[i]<=N,下标从1开始。
A和B做N轮游戏,轮数下标从1开始。在第i轮,玩法如下:
1. A指定一个正整数ki,告诉B。
2. B指定一个1~N之间的数,写在黑板上。
3. 做如下操作ki次:把黑板上的数字x替换成a[x]。
操作结束后,如果黑板上的数字是i,B赢;否则A赢。
请问在A和B都做最优操作的情况下,B能赢多少次。
"""


#       ms
def solve():
    n, = RI()
    a = RILST()
    g = [[] for _ in range(n + 1)]
    degree = [0] * (n + 1)
    for i, v in enumerate(a, start=1):
        g[i].append(v)
        degree[v] += 1
    q = deque([i for i, v in enumerate(degree) if i and v == 0])
    ans = n
    while q:
        ans -= 1
        u = q.popleft()
        for v in g[u]:
            degree[v] -= 1
            if degree[v] == 0:
                q.append(v)
    print(ans)

七、F - Simultaneous Swap

链接: F - Simultaneous Swap

1. 题目描述

在这里插入图片描述

2. 思路分析

群里大佬说正解是逆序对,我没看懂,直接找性质做的。没想到直接交就TLE了,优化了一下就过了
这里吐槽一下for x in set:break 是会TLE的,看来虽然提前break但没用,依然是O(n)。
  • 首先a和b的元素对应数量必须相同,用sorted(a) != sorted(b)特判。
  • 手玩发现,如果有1个元素出现两次,那么按照case1的方法交换,一定可以使另外一个位置相同,此时必Yes。
  • 然后是模拟验证所有元素只出现一次的情况:
    • 使第一个位置元素相同,只需找到一个还没处理的数字,找到它在ab分别的位置,把这个元素换过来。
    • 然后依次处理第2\3\4…个位置。如果遇到本来就相同就不用动。
    • 直到处理完倒数第三个位置,这时后两个位置如果不同则不行,如果完全相同才行,因为他们无法做出有用的交换了。

  • 逆序对做法:如果ab的逆序对数奇偶性相同,则可以交换;否则不能交换。
  • 当然前几个特判依然需要。

3. 代码实现

PROBLEM = """https://atcoder.jp/contests/abc296/tasks/abc296_f
给你N(3<=N<=2e5),和两个长度为n的数组a,b。
你可以做如下操作任意次或不做:
选下标j!=i!=k,同时交换:在a中交换a[i]a[j].再b中交换b[i]b[k]
若可以使a完全等于b输出Yes,否则输出No。
"""


#       ms
def solve():
    n, = RI()
    a = RILST()
    b = RILST()

    if a == b:
        return print('Yes')
    if sorted(a) != sorted(b):
        return print('No')
    if len(set(a)) < n:
        return print('Yes')
    posa = {v: i for i, v in enumerate(a)}
    posb = {v: i for i, v in enumerate(b)}
    left = set(a)

    def get(x, y):
        s = set()
        ans = 0
        nonlocal left
        for _ in range(3):
            p = left.pop()
            if x != p != y:
                ans = p
            s.add(p)
        left |= s

        return ans

    for i in range(n - 2):
        if a[i] == b[i]:
            left.remove(a[i])
        else:
            v = get(a[i], b[i])
            pa = posa[v]
            pb = posb[v]
            left.remove(v)
            posa[a[i]] = pa
            posb[b[i]] = pb
            a[pa] = a[i]
            b[pb] = b[i]
    if a[-1] == b[-1] and a[-2] == b[-2]:
        return print('Yes')
    print('No')
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值