[acwing周赛复盘] 第 110 场周赛20230701

本文复盘了ACWing第110场周赛的三道题目,包括求和问题的模拟解法,利用双指针解决的三角形数问题,以及运用树状数组优化的动态规划问题。每道题目分别从题目描述、思路分析和代码实现三个方面进行了详细阐述。
摘要由CSDN通过智能技术生成

总结

  • 状态不对,把自己写懵了。
  • T1 模拟币
  • T2 对向双指针/两数之和。
  • T3 树状数组优化dp。
  • 在这里插入图片描述
    在这里插入图片描述

5044. 求和

链接: 5044. 求和

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 模拟。

3. 代码实现

# Problem: 求和
# Contest: AcWing
# URL: https://www.acwing.com/problem/content/5047/
# Memory Limit: 256 MB
# Time Limit: 1000 ms

import sys
import random
from types import GeneratorType
import bisect
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, reduce
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')
# print = lambda d: sys.stdout.write(str(d) + "\n")  # 打开可以快写,但是无法使用print(*ans,sep=' ')这种语法,需要print(' '.join(map(str, p))),确实会快。

DIRS = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # 右下左上
DIRS8 = [(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0),
         (-1, 1)]  # →↘↓↙←↖↑↗
RANDOM = random.randrange(2**62)
MOD = 10**9 + 7
# MOD = 998244353
PROBLEM = """
"""


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


def bootstrap(f, stack=[]):

    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to

    return wrappedfunc


#       ms
def solve():
    n,s = RI()
    a = RILST()
    if sum(a) == s:
        print('YES')
    else:
        print('NO')


if __name__ == '__main__':
    t = 1
    if t:
        t, = RI()
        for _ in range(t):
            solve()
    else:
        solve()

5045. 三角形数

链接: 5045. 三角形数

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 题意转化一下:有一个数列数字均为n(n+1)//2,求从中找两个数加起来等于x。
  • 那么显然就是两数之和的双指针思路,从左右两边同时指针寻找,和大就动右指针,和小动左指针。

3. 代码实现

def solve():
    n, = RI()
    l, r = 1, int((2 * n) ** 0.5)
    while l <= r:
        x = l * (l + 1) // 2 + r * (r + 1) // 2
        if x == n:
            return print('YES')
        elif x < n:
            l += 1
        else:
            r -= 1
    print('NO')

5046. 智商药

链接: 5046. 智商药

1. 题目描述

在这里插入图片描述

2. 思路分析

  • 手玩一下发现,首先数据里必须有l0和rn,否则没有方案数。不过实现时到时不用管这个条件。
  • 仔细想一下,对于一种药(l,r)来讲,吃它只能到达状态r,需要从[l,r-1]这个连续状态转移而来。这里边所有数字都应该是前边一种药的r。
  • 那么按照r排序,状态就满足无后效性了,发现每个状态的前驱状态都是连续的,可以用区间求和方法来优化。比如树状数组。
  • 用树状数组来当dp数组。
  • 令dp[i]为吃到第i种药的方案数,那么dp[i] = sum(dp[x,y]),其中x,y为对应右端点在[l,r-1]区间的药的下标。

3. 代码实现

class BinIndexTree:
    """    PURQ的最经典树状数组,每个基础操作的复杂度都是logn;如果需要查询每个位置的元素,可以打开self.a    """

    def __init__(self, size_or_nums):  # 树状数组,下标需要从1开始
        # 如果size 是数字,那就设置size和空数据;如果size是数组,那就是a
        if isinstance(size_or_nums, int):
            self.size = size_or_nums
            self.c = [0 for _ in range(self.size + 5)]
            # self.a = [0 for _ in range(self.size + 5)]
        else:
            self.size = len(size_or_nums)
            # self.a = [0 for _ in range(self.size + 5)]
            self.c = [0 for _ in range(self.size + 5)]
            for i, v in enumerate(size_or_nums):
                self.add_point(i + 1, v)

    def add_point(self, i, v):  # 单点增加,下标从1开始
        # self.a[i] += v
        while i <= self.size:
            self.c[i] += v
            self.c[i] %= MOD
            i += i & -i

    # def set_point(self, i, v):  # 单点修改,下标从1开始 需要先计算差值,然后调用add
    #     self.add_point(i, v - self.a[i])
    #     self.a[i] = v

    def sum_interval(self, l, r):  # 区间求和,下标从1开始,计算闭区间[l,r]上的和
        return (self.sum_prefix(r) - self.sum_prefix(l - 1)) % MOD

    def sum_prefix(self, i):  # 前缀求和,下标从1开始
        s = 0
        while i >= 1:
            s += self.c[i]
            s %= MOD
            # i -= i&-i
            i &= i - 1
        return s

    def min_right(self, i):
        """寻找[i,size]闭区间上第一个正数(不为0的数),注意i是1-indexed。若没有返回size+1;复杂度O(lgnlgn)"""
        p = self.sum_prefix(i)
        if i == 1:
            if p > 0:
                return i
        else:
            if p > self.sum_prefix(i - 1):
                return i

        l, r = i, self.size + 1
        while l + 1 < r:
            mid = (l + r) >> 1
            if self.sum_prefix(mid) > p:
                r = mid
            else:
                l = mid
        return r

    def kth(self, s):
        """返回<=s的最小下标"""
        pos = 0
        for j in range(18, -1, -1):
            if pos + (1 << j) <= self.size and self.c[pos + (1 << j)] <= s:
                pos += (1 << j)
                s -= self.c[pos]
        return pos

    def lowbit(self, x):
        return x & -x


#       ms
def solve():
    n, m = RI()
    a = []
    for _ in range(m):
        a.append(RILST())

    a.sort(key=lambda x: x[1])
    b = [y for _, y in a]
    f = BinIndexTree(m)
    ans = 0
    for i, (l, r) in enumerate(a, start=1):
        x = bisect_left(b, l) + 1
        y = bisect_right(b, r - 1) - 1 + 1
        if x <= y:
            f.add_point(i, f.sum_interval(x, y))
        if l == 0:
            f.add_point(i, 1)

        if r == n:
            ans = (ans + f.sum_interval(i, i)) % MOD
    print(ans)

六、参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值