zoukankan      html  css  js  c++  java
  • 【题解】洛谷 P4339 [ZJOI2018]迷宫 (UOJ#375)

    这篇文章的所有图片使用 (operatorname{Graphviz}) 生成,在此鸣谢。

    初稿 on 2020-05-28 16:26:54

    构造有 (K) 个点的“基本方案”

    考虑构造一个有 (∞) 个点的方案。
    为了方便,我们假设题目中的 (1) 号点为 (0) 号点。
    对于一个点 (x),我们使它的第 (i(iin[0,m-1])) 条出边指向节点 (x · m + i)
    为了使得路径能回到 (0) 号点,我们使所有满足 (xequiv 0 pmod {K}) 的点的第 (0) 条出边都指向 (0)。容易证明这样一定是可行的。
    于是我们容易想到,如果把每个节点的编号都模上 (K),我们将得到一个有 (K) 个点的方案。

    整理一下:
    为了得到一个有 (K) 个点的方案,对于每一个节点 (xin[0,K-1]),其第 (i(iin[0,m-1])) 条出边指向节点 ((x · m + i)mod K)

    “缩点”

    我们发现,样例中第一组数据是满足我们的构造方案的,但是第二、三组数据的答案均比 (n=K) 优秀。
    考虑用我们的方案先构造一个 (m=2,n=K=4) 的方案
    m2k4-1.PNG
    其中黑色边表示 (0) 号边,红色边表示 (1) 号边。
    我们能发现什么东西么?
    基本事实 1 如果两个点可以到达的点集相同,那么这两个点可以被缩成 1 个点。
    所谓“可以到达的点集相同”,形式化的说,对于两个点 (a,b),是指 (forall {0le i<m}),有 ((a · m + i)mod K = (b · m + i)mod K) 或者 ((a · m + i)mod K = b) 或者 ((b · m + i)mod K = a)
    感性理解,就是缩点之后, (forall {0le i<m}), 被缩点的第 (i) 条出边指向的目标点不会不同。
    如样例第二组数据,可以对 (1,3) 号节点缩点,变成这样:
    m2k4-2.PNG
    可以发现,这就是样例解释。
    (0) 号点对应样例 (1) 号点,(2) 号点对应样例 (2) 号点,(13) 号点对应样例 (3) 号点)

    再给一张大一点的图(对应样例第三组数据)。
    黑色边表示 (0) 号边,红色边表示 (1) 号边,绿色边表示 (2) 号边,蓝色边表示 (3) 号边,紫色边表示 (4) 号边,橙色边表示 (5) 号边。
    col-释.PNG
    (m=6,K=8) 时可以构造出一个 (n=8) 的基本方案
    m6k8-1.PNG
    缩点后得到一个 (n=5) 的最佳方案
    m6k8-2.PNG
    可以手玩一下验证其正确性
    即样例输出

    关于 (0) 号节点

    注意到上面几张图中 (0) 号节点都没有被缩点。
    注意到这个节点比较的特殊,不能被缩点。
    为什么?
    因为 (0) 号节点可以被作为迷宫的结束点,而别的任意一个节点均不满足这一性质,自然不能与 (0) 号节点一起缩点。
    但是其他与 (0) 可以到达的点集相同的点之间时可以缩点的。
    口说无凭,以图解释。
    (m=3,K=3) (黑色边表示 (0) 号边,红色边表示 (1) 号边,绿色边表示 (2) 号边)
    基本方案 (n = 3)
    m3k3-1.PNG
    缩点后 (n = 2)
    m3k3-2.PNG

    什么时候可以缩点

    对于两个可以缩的点 (a, b)
    因为 (forall 0le i<m,a·m+iequiv b·m+ipmod K)
    所以若有 (a·mequiv b·mpmod K) 则可以缩点
    (g = gcd(m, K))
    则有 (a·frac{m}{g}equiv b·frac{m}{g}pmod {frac{K}{g}})
    (m^′=frac{m}{g},K^′=frac{K}{g})
    则有 (a·m^′equiv b·m^′pmod {K^′})
    此时 (m^′,K^′) 互质
    那么若有 (a otequiv 0 pmod {K^′})(b otequiv 0 pmod {K^′}),就一定有 (aequiv b pmod {K^′})

    证明:
    不妨设 (a=cK^′+e,b=dK^′+f)
    其中 (e,fin[0,K^′))(c,d,e,f) 均为自然数
    ( herefore aequiv epmod {K^′},bequiv fpmod {K^′})
    (ecause a·m^′equiv m^′(cK^′+e)equiv m^′cK^′+m^′eequiv m^′epmod {K^′},)
    (b·m^′equiv m^′(dK^′+f)equiv m^′dK^′+m^′fequiv m^′fpmod {K^′})
    ( herefore m^′e=m^′fpmod {K^′})
    (ecause m^′,K^′) 互质且 (a otequiv 0 pmod {K^′})(b otequiv 0 pmod {K^′})
    ( herefore e=f)
    ( herefore aequiv bpmod {K^′})
    因此原命题得证

    考虑计算哪些点可以缩点。
    对于点 (x)(x) 可以化为 (x=yK^′+z),其中 (zin[0,K^′))(y,z) 均为自然数。
    一个点可以被缩点,当且仅当 (z ot ={0})
    此时能与点 (x) 缩为一点的点 (z) 都相等。
    而对于 (xin [0,K))(z) 相等的数有 (g) 个。
    因此,对于同一个 (z) 对应的 (g) 个点,缩为一点的时候会减少 (g-1) 个点。
    因为 (zin [1,K^′)) 时可以缩点,所以有 (K^′-1) 组可以缩的点,这些点会使答案减少 ((g-1)(K^′-1))
    所以最终答案为 (K - (g-1)(K^′-1)),其中 (g = gcd(m, K))

    发现这一性质满足样例的三组数据。
    提交后,我们获得了 (0) 分的好成绩。

    论证之不严密

    我们发现,有些点是可以被二次缩点的。
    观察下列 (m=2,K=8) 的图(黑色边表示 (0) 号边,红色边表示 (1) 号边)
    基本方案 (n=8)
    m2k8-1.PNG
    一次缩点 (n=6)
    m2k8-2.PNG
    二次缩点 (n=5)
    m2k8-3.PNG
    这时候我们的原计算方案就失效了,需要寻求新的解决方案。

    递归缩点

    由于需要多次缩点,考虑递归求解。

    发现两个点 (x,y) 能被二次缩点,一定需要满足 (x·m^u=y·m^u pmod{K^′})
    不妨设当前缩点后还剩下 (r) 个点,不妨设其编号为 (1dots r) (排除 (0) 号节点)。
    需要注意,在前一轮缩点中没有被缩点的点,下一轮一定不可能被缩点(自行感性理解,或者结合上面 (m=2,K=8) 的图理解,不会证)
    考虑设计一个函数 solve,返回缩点中去除了几个节点。
    分析这个函数需要的参数。
    由于每次一定进行了缩点,(r) 值变化, (r) 需要作为参数。
    同时需要参数 (t=m^u) (其中 (u) 的实际意义是递归次数,但不会存储)
    还需要参数 (K)。因为每次计算时都使用 (K^′),所以应该将 (K^′) 传给下一层递归函数。
    同时由于我们下传的 (K) 每次都除以了 (g),下传的 (t) 也应该一并除以 (g) (实际原因:(t) 实际不是 (u) 次递归中 (m) 的乘积,而应该是 (u) 次递归中 (m^′) 的乘积)
    考虑到如果 (g=1),那么一定不能缩点(显然不证)
    考虑到如果 (r<K^′),点的个数小于“出边构成的集合”的种类数,也不能缩点。
    因此上述两种情况都应该返回 (0) 以表示缩去了 (0) 个点。
    考虑到缩点的时候,缩得的 (K^′) 个点中有 (t) 个点是不可能再次被缩的,因此传给下一层 solve(r) 应为 (K^′-t)

    考虑递归终止条件。
    假设 (r<0) 显然应该终止。
    但是 如果这样计算,(t) 有可能会爆 int64(实测 (80) (pts)),所以我们需要再上一层就判断传给下一层的 (r) 的正负,而且不应当用整数乘法,应当用浮点数除法
    (事实上,你可以使用 __int128 解决问题,但不能保证 ZJOI i3 老年机会不会返回 (0) 分的好成绩)

    solve 的返回值应为下一层递归的结果加上 (r-K^′)(到这一层剩余的点减去缩得的点个数)

    代码放 solve 函数和 main 函数

    其中 K_ 表示 (K^′)m_ 表示 (m^′)

    typedef long long int64;
    
    int64 solve(int64 r, int64 t, int64 K) {
        int64 g = gcd(m, K), K_ = K / g, m_ = m / g;
        if (g == 1 || r <= K_) return 0;
        if ((double)K_ / m_ < t) return r - K_;
        t *= m_;
        return solve(K_ - t, t, K_) + r - K_;
    } 
    int main()
    {
        T = read<int>();
        while (T--) {
            m = read<int64>();
            K = read<int64>();
            int64 g = gcd(m, K), K_ = K / g;
            write(K - solve(K - 1, 1, K), 10);
        }
        return 0;
    }
    
  • 相关阅读:
    Java代码编译执行的过程
    《Redis核心原理与实战》学习笔记7——有序集合使用与内部实现原理
    《Redis核心原理与实战》学习笔记6——集合使用与内部实现原理
    Eureka服务治理-注册中心和注册服务
    SpringCloud微服务概述
    MySQL索引问答10道
    MySQL基础问答15道
    《Redis核心原理与实战》学习笔记5——列表使用与内部实现原理
    《Redis核心原理与实战》学习笔记4——字典使用与内部实现原理
    《Redis核心原理与实战》学习笔记3——字符串使用与内部实现原理
  • 原文地址:https://www.cnblogs.com/hkxadpall/p/luogu-P4339.html
Copyright © 2011-2022 走看看