• UOJ pjudge LOJ 试题乱做 Part6


    这个人咋啥也不会捏, 别 \(restart\) 了, 直接 \(remake\) 吧.


    \(\text{【LOJ\#3005】「JOISC 2015 Day 4」Limited Memory}\)

    \(\color{green}{\text{[EASY]}}\)

    虽然简单, 但这种题很有意思啊!

    很明显的是 \(n\) 是奇数一定无解.

    注意到数据范围 \(n\) 只有 \(100\) , 但询问次数支持 \(\mathcal{O}(n^2)\) 级别的, 允许记录的数字虽然有很强的二进制记录的导向, 比如记录三个 \(\mathcal{O}(n)\) 级别的数和一个 \(\mathcal{O}(1)\) 级别的数, 但我们发现 \(2^7=128\) , 也就是说有很多信息被我们浪费了, 但是注意到, \(4\times n^3 < 2^{22}\) , 所以新的思路应该是记录三个 \(\mathcal{O}(n)\) 级别的数和一个大小不超过 \(3\) 的数, 所得到的信息是前者的两倍.

    括号匹配并没有什么高妙算法, 还是考虑最初始的用栈匹配, 这引导我们想出一个最初始的算法, 模拟匹配过程, 先找到一个左括号, 然后往后寻找匹配它的右括号.

    此时匹配时会有两种可能,

    • 下一个是右括号, 此部分匹配结束或者知道是不合法串.
    • 下一个还是左括号, 此时我们应该去匹配新的右括号, 直到匹配上, 再从那个位置继续匹配这个左括号.

    也就是说, 当一个匹配成功的时候, 假设位置为 \(x\) , 考虑怎么找到前一个未被匹配的左括号, 直接往左边暴力遍历同时做前缀和即可, 第一次小于 \(0\) 的位置一定是我们要找的左括号的位置.

    重新整理一下要带的参数, 当前位置 \(a\) , 在往前找没匹配过的左括号时我们需要带上开始找左括号时的位置 \(x\) , 这样就不用从未匹配的位置再跑一遍, 记作 \(b\) , 在往前找的时候我们得记前缀和 \(c\) , 还有目前状态 \(d\) , 状态一共有 \(4\) 种.

    1. 前面所有的括号都匹配上了, 要寻找新的左括号.
    2. 往左寻找左括号.
    3. 匹配左括号 [ 中.
    4. 匹配左括号 ( 中.

    这样总询问数是 \(\mathcal{O}(n^2)\) , 总状态数是 \(\mathcal{O}(4\times n^3)\) , 写的时候注意判断边界以及细节.


    \(\text{【LOJ\#3006】「JOISC 2015 Day 4」防壁}\)

    \(\color{blue}{\text{[NORMAL]}}\)

    本来想给 \(\color{red}{\text{[HARD]}}\) 的, 因为看了 \(800\) 年题解, 但后来想想还是算了, 这里阐述一下我看的题解并加一些细节的处理.

    首先发现每个块都是独立的, 对于每个块, 我们肯定是不用移动就不移动, 要移动一定是一个端点触碰到位置, 由此我们得到了一个 \(\mathcal{O}(nm)\) 的暴力.

    先考虑 Subtask2 怎么做. 对于线段 \([0,t]\) 我们定义一个询问 \(x_i\) 是重要的当且仅当该线段为了覆盖 \(x_i\) 而移动了.

    根据定义, 不难得到以下结论.

    1. 如果一个询问点对于线段 \([0,t]\) 是重要的, 那么 \(x_i\) 对于线段 \([0,t-1]\) 也是重要的.
    2. 如果询问点 \(x_i\) 对于线段 \([0,t]\) 是重要的, 并且满足关系 \(x_{i-1} < x_i\) 那么该线段在询问点 \(x_i\) 之后覆盖的位置为 \([x_i-t,x_i]\) , 若 \(x_{i-1} > x_i\) 则是 \([x_i,x_i+t]\) .

    对于线段 \([0,t]\) , 容易发现只用考虑对该线段重要的询问点, 令这些询问点组成的序列为 \(a_1,\dots,a_k\) , 容易发现这 \(k\) 个线段对 \([0,t]\) 都有贡献, 不妨先假设线段 \([0,t]\) 移动相邻询问点都要移动 \(|a_{i+1}-a_{i}|\) 格, 但实际上并不一定要走这么远, 发现如果 \([a_{i-1} < a_i]\not= [a_i<a_{i+1}]\) 即移动方向改变, 形成一个拐点, 那么线段 \([0,t]\) 就可以少移动 \(t\) 格, 设拐点数量为 \(cnt\) , 那么线段 \([0,t]\) 的答案即为 \(\sum\limits_{i=1}^{k}{|a_{i}-a_{i-1}|}-cnt\cdot t\) , 若我们把线段按长度排序, 那么拐点数量一定是逐渐变少的, 如果我们能维护每次少了哪些拐点, 我们就可以使用链表进行简单的修改, 问题就解决了.

    对于如何找到这些拐点, 我们这样考虑, 由于按长度排序后拐点数量逐渐变少, 也就是说对于每个询问点, 有一个最长的线段使得该询问点是重要的, 我们记该线段为 \(f(x_i)\) , 注意到 \(f(x_i)\) 的极值性, 考虑整体二分. 对于线段 \([0,t]\) 我们要判断 \(x_i\) 是否重要, 那就必须得求出该线段在询问 \(x_i\) 之前的位置, 也是上一个对线段 \([0,t]\) 重要的询问点 \(x_{j}\) , 并且需要直到上一次的移动方向, 才可以确定位置, 具体实现可以随便找篇代码看.

    至此我们得到了 Subtask2 的做法, 时间复杂度为 \(\mathcal{O}(m\log{n})\) .

    我们继续考虑一般情况 \([a_i,b_i]\) , 令 \(t_i=b_i-a_i\) , 我们考虑算出 \([0,t_i]\) 的答案以及 \([a_i,b_i]\) 的答案减去 \([0,t_i]\) 的答案 (这一步转化很巧妙啊, 用已知的东西和易得的东西来求答案) . 至于怎么求解后者, 我们观察两个线段的移动, 我们发现, 若存在两个询问点 \(q<p\) 满足 \(|x_p-x_q| > t_i\) , 则在某一个操作之后, 它们一定会重合, 不难知道第一次重合的位置就是最小的 \(p\) , 若不存在这样的 \(p\) 则两条线段只会向一个方向移动, 这是容易知道答案的. 不难知道在还没重合的时候也是两条线段只会向一个方向移动.

    我们直接二分出 \(p\) 就可以很容易算出 \([a_i,b_i]\) 的答案与 \([0,t_i]\) 的答案之差了. 具体算法是直接左端点和左端点相减, 右端点和右端点相减, 选较大的值, 和 \(0\)\(\max\) .

    时间复杂度 \(\mathcal{O}(n\log{m}+m\log{n})\) .

  • 相关阅读:
    js 导航栏多项点击显示下拉菜单代码
    当你工作与生活迷茫时可以来看看 shuke
    vs code使用指南
    两列
    三列浮动中间宽度自适应
    两列右列宽度自适应
    word文档巧替换(空行替换、空格替换、软回车替换成硬回车)
    统计单元格内指定的字符数方法 ,方法 一好用
    umi ui 构建时出现 spawn sh ENOENT 报错的解决方法
    新的博客,声明一下以前的域名作废了
  • 原文地址:https://www.cnblogs.com/Lonely923/p/16907798.html
Copyright © 2020-2023  润新知