首页 > 编程语言 >万能欧几里得算法学习笔记

万能欧几里得算法学习笔记

时间:2023-07-07 12:55:07浏览次数:42  
标签:lfloor right frac cdot 欧几里得 rfloor 算法 万能 left

万能欧几里得算法

万能欧几里得算法用于解决一类与\(\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor\)有关的和式求解问题,例如类欧几里得算法中提到的和式就属于此类问题。但万能欧几里得算法从几何意义出发能处理更广泛的和式类型。例如\(\sum_{x=0}^{l}A^xB^{\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor}\quad A,B\subseteq \mathbb{R}^{n\times n}\)。

我们在平面直角坐标系中作出一条直线\(y=\frac{p\cdot x+r}{q}\)。以\(y=\frac{5x+3}{4}\)为例

我们注意到这条直线与平行于坐标轴的直线有若干个交点。与横线的交点(紫色),与竖线的交点(红色),如果同时与横线和竖线相交,我们约定先与横线相交再与竖线相交,这么做的原因我们等会会看到。

我们要求解的和式就可以看做,从初始状态开始,遇到横线做一种操作,遇到竖线做另一种操作最终得到答案。因为遇到横线意味着\(\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor\)会增加\(1\),遇到竖线意味着\(x\)会增加\(1\)。

以开头提到的\(\sum_{x=0}^{l}A^xB^{\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor}\quad A,B\subseteq \mathbb{R}^{n\times n}\)为例,我们设定一个三元组\(\left( C,D,ans \right)\)来记录答案。

初始时\(C=D=I_n\quad ans=B^{\left\lfloor \frac{r}{q} \right\rfloor}\),我们只需要关注\(\left( 0,l \right]\)范围内的交点,然后每经过一根横线,我们需要将\(D\text{右乘}B\),\(C\text{和}ans\text{不变}\)。每经过一条竖线,我们需要将\(C\text{左乘}A,D保持不变,ans+A\cdot C\cdot D \rightarrow ans_{new}\)

对三元组形式化的理解是,\(C\)表示当前操作序列\(A^x\)的值,\(D\)表示当前操作序列\(B^{\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor}\)的值,\(ans\)表示当前操作序列对答案的贡献。这里我们可以明白一开始约定碰到与横线竖线同时相交的点要视作先碰横线再碰竖线,因为在碰到竖线的时候意味着\(x\)增加了\(1\),需要更新答案了,先要把\(\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor\)更新到最新。

我们继续把上述操作用标准化的语言描述,定义三元组的乘法,\(\left( a,b,c \right) \ast (d,e,f)=(a\cdot d,b\cdot e,c+a\cdot f\cdot b)\quad a,b,c,d,e,f\subseteq \mathbb{R}^{n\times n}\)碰上横线进行的操作是乘三元组$\left( I_n,B,O_n \right) \(,碰上竖线进行的操作是乘三元组\)\left( A,I_n,A \right) $。容易证明这个三元组的乘法具有结合律,事实上这也是万能欧几里得算法能解决问题的必要条件。

有了上面的储备,接下来就来看看怎么样加速求解的过程。方便起见,我们用\(s_0\)表示碰到横线需要执行的操作,\(s_1\)表示碰到竖线需要执行的操作。用\(solve(p,q,r,l,s_0,s_1)\)描述问题的求解过程。

  • case0:对于\(r\)的处理。
    由于我们只关心交点到来的顺序以及交点的种类,因此将函数上下平移整数个单位是不影响答案的,最开始的若干根横线除外,这个需要单独考虑。我们把\(r\)修改为\(r\%q\),后面我们都默认\(r<q\)。

  • case1:\(p\ge q\)
    在这种情况下,我们可以发现:

    \[\left\lfloor \frac{p\cdot (x +1)+r}{q} \right\rfloor-\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor=\left\lfloor \frac{p\cdot x +(p\%q)+r}{q} \right\rfloor-\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor+\left\lfloor \frac{p}{q} \right\rfloor \ge \left\lfloor \frac{p}{q} \right\rfloor \]

    这说明在遇到一根竖线之前我们至少会遇到\(\left\lfloor \frac{p}{q} \right\rfloor\)条横线。这样就可以把这\(\left\lfloor \frac{p}{q} \right\rfloor\)个\(s_0\)操作和一个\(s_1\)操作合并成一个新的\(s_1\)操作。这样每条竖线之前会有\(\left\lfloor \frac{p}{q} \right\rfloor\cdot x\)条横线被合并

    \[\left\lfloor \frac{p\cdot x+r}{q} \right\rfloor-\left\lfloor \frac{p}{q} \right\rfloor\cdot x=\left\lfloor \frac{(p\%q)\cdot x+r}{q} \right\rfloor \]

    因此\(solve(p,q,r,l,s_0,s_1)=solve(p\%q,q,r,l,s_0,s_0^{\left\lfloor \frac{p}{q} \right\rfloor}s_1)\)

  • case2:\(p<q\)
    我们可以想到把坐标轴翻转,把\(y=\frac{p\cdot x+r}{q}\)变为\(y=\frac{q\cdot x-r}{p}\)同时交换\(s_0,s_1\)这样就可以回归到case1中去了。但翻转过后我们发现三个小问题,第一个是对于同时和横线竖线相交的点,翻转之后变成了先做\(s_1\)再做\(s_0\)这与我们的期望不符,第二个是直线在\(y\)轴上的截距变成了负的;第三个是原先我们是以竖线结尾的,现在变成以横线结尾。这些问题导致处理起来会有麻烦。(下面的推到过程并不适用于\(p=1\)的情况,但我们之后会看到用下面推到过程得出的算法对于\(p=1\)确是正确的)
    我们先来解决第一个问题,

    \[\forall y \subseteq \mathbb{N}^*\text{且}q\cdot y-r\neq 0 \pmod p \\ \frac{q\cdot y-r}{p}-\left\lfloor \frac{q\cdot y-r}{p} \right\rfloor=\frac{q\cdot y-r-\left\lfloor \frac{q\cdot y-r}{p} \right\rfloor\cdot p}{p}=\frac{(q\cdot y-r)\% p}{p} \ge \frac{1}{p} \]

    因此我们可以将原函数向左平移\(\frac{1}{p}\)个单位再反转,这并不影响答案,同时又解决了同时和横线竖线相交的点。直线变成\(y=\frac{qx-r-1}{p}\)
    再来看剩下的两个问题。事实上,第二个问题对应翻转前\(y<1\)的情况,第三个问题对应翻转前\(y\ge \left\lfloor \frac{p\cdot l+r}{q} \right\rfloor\)的情况。我们可以掐头去尾把这两部分单独拿出来考虑。

    \[y=\frac{p\cdot x+r}{q}<1 \Leftrightarrow 0<x\leq \left\lfloor \frac{q-r-1}{p} \right\rfloor \\ y=\frac{p\cdot x+r}{q}\geq\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor\Leftrightarrow \left\lfloor \frac{\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor\cdot q-r-1}{p} \right\rfloor+1 \leq x \leq l \]

    这样我们只需要专注于\(\left( 1,\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor \right]\)内的交点即可。为了与最初的问题适配,我们把直线向左平移一个单位,并计算\(\left( 0,\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor-1 \right]\)内的交点。直线变成\(y=\frac{q\cdot x+q-r-1}{p}\)
    因此\(solve\left( p,q,r,l,s_0,s_1\right)=s_1^{\left\lfloor \frac{q-r-1}{p} \right\rfloor}\cdot s_0\cdot solve\left(q,p,q-r-1,\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor-1\right)\cdot s_1^{l-\left\lfloor \frac{\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor\cdot q-r-1}{p} \right\rfloor}\)

  • case3:\(p=1\)
    我们再来看\(p=1\)的情况,\(p=1\)时,当\(y\)取到整数时,\(x\)也必然是整数,操作过程是规律性的\(q\)条竖线\(1\)条横线,\(q\)条竖线\(1\)条横线,\(\cdots\) 在case2的算法中我们掐头时,少算了经过\(y=1\)这个整点的一条竖线,然而在经过其他整点时由于横线竖线顺序是反的,刚好把这个误差补了回来,在去尾时,多算了经过\(y=\left\lfloor \frac{p\cdot l+r}{q} \right\rfloor\)时的一条竖线,刚好又把最后的误差给补足了。所以虽然case2的推导过程对\(p=1\)不适用,但导出的算法恰好适合\(p=1\)。真是太奇妙了

现在我们完成了整个万能欧几里得算法,他和传统欧几里得算法的过程类似,时间复杂度是合并两个操作的时间复杂度乘上\(O(\log_2p)\)

code:万能欧几里得

#include <cstdio>
#include <cstring>
#include <iostream>
#define int long long

using namespace std;

const int mo = 998244353;

struct mat {
    int a[25][25];
    int n;
    void init(int n) {
        memset(a, 0, sizeof(a));
        this->n = n;
    }
    void read() {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                scanf("%lld", &a[i][j]);
            }
        }
    }
    void setI(int n) {
        this->n = n;
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= n; i++) {
            a[i][i] = 1;
        }
    }
    mat operator+(const mat& o) const {
        mat ans;
        ans.init(n);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                ans.a[i][j] += o.a[i][j];
                ans.a[i][j] += a[i][j];
                ans.a[i][j] %= mo;
            }
        }
        return ans;
    }
    mat operator*(const mat& o) const {
        mat ans;
        ans.init(n);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                for (int k = 1; k <= n; k++) {
                    ans.a[i][j] += 1ll * a[i][k] * o.a[k][j] % mo;
                    ans.a[i][j] %= mo;
                }
            }
        }
        return ans;
    }
    void print() {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j < n; j++) {
                printf("%lld ", a[i][j]);
            }
            printf("%lld\n", a[i][n]);
        }
    }
} A, B;

struct pp {
    mat a;
    mat b;
    mat ans;
    int n;
    pp(int n) {
        a.setI(n);
        b.setI(n);
        ans.init(n);
        this->n = n;
    }
    pp(mat o, mat p, mat q, int n) {
        a = o;
        b = p;
        ans = q;
        this->n = n;
    }
    pp setA() {
        a.init(n);
        b.setI(n);
        a = ans = A;
        return *this;
    }
    pp setB() {
        a.setI(n);
        b.init(n);
        ans.init(n);
        b = B;
        return *this;
    }
    pp fix() {
        for (int i = 1; i <= n; i++) {
            ans.a[i][i]++;
            ans.a[i][i] %= mo;
        }
        return *this;
    }
    pp operator*(const pp& o) const {
        return pp(a * o.a, b * o.b, ans + a * o.ans * b, n);
    }
    pp pow(int p) {
        pp yu = pp(n);
        pp o = *this;
        while (p) {
            if (p & 1) yu = yu * o;
            o = o * o;
            p >>= 1;
        }
        return yu;
    }
};

int p, q, r, l, n;

pp slove(int p, int q, int r, int l, pp s0, pp s1) {
    r = r % q;
    if (p >= q) return slove(p % q, q, r, l, s0, s0.pow(p / q) * s1);
    int m = ((__int128)p * l + r) / q;
    if (m == 0) return s1.pow(l);
    return (s1.pow((q - r - 1) / p) * s0 *
            slove(q, p, q - r - 1, m - 1, s1, s0) *
            s1.pow(l - ((__int128)m * q - r - 1) / p));
}

signed main() {
    scanf("%lld%lld%lld%lld%lld", &p, &q, &r, &l, &n);
    A.init(n);
    A.read();
    B.init(n);
    B.read();
    pp s0 = pp(n).setB();
    pp s1 = pp(n).setA();
    pp ans = pp(n);
    ans.ans = s0.pow((p+r)/q).b;
    ans = ans * s0.pow((p + r) / q) * slove(p, q, p + r, l - 1, s0, s1);
    (A * ans.ans).print();
    return 0;
}

标签:lfloor,right,frac,cdot,欧几里得,rfloor,算法,万能,left
From: https://www.cnblogs.com/clapp/p/17534663.html

相关文章

  • 常用算法记录
    二叉树遍历https://leetcode.cn/problems/binary-tree-preorder-traversal/solutions/87526/leetcodesuan-fa-xiu-lian-dong-hua-yan-shi-xbian-2/递归解法前序遍历publicstaticvoidpreOrderRecur(TreeNodehead){if(head==null){return;}......
  • 几个经典算法的概念
    动态规划(DynamicProgramming) 一、动态规划的基本思想:   动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题......
  • 数据结构(算法)【7月6日】
    一、算法的基本概念:1、算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。2、算法的特性:(1)有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成;【算法是有穷的,程序是无穷的】(2)确定性:算法中每条指令必须有确切的含义,......
  • SRGAN图像超分重建算法Python实现(含数据集代码)
    摘要:本文介绍深度学习的SRGAN图像超分重建算法,使用Python以及Pytorch框架实现,包含完整训练、测试代码,以及训练数据集文件。博文介绍图像超分算法的原理,包括生成对抗网络和SRGAN模型原理和实现的代码,同时结合具体内容进行解释说明,完整代码资源文件请转至文末的下载链接。完整......
  • 数据结构与算法1-2
    王争,西安交通大学计算机专业本科毕业时候编程水平其实是很差的。读研究生看《算法导论》。从此我对算法的“迷恋”便一发不可收拾。之后,我如把图书馆里几乎所有数据结构和算法书籍都读了一遍。我边读边练。没多久我就发现,写代码时会不由自主考虑很多性能方面的问题。我写出时间......
  • springcloud - ribbon简单提点 + 手写轮询算法
    ribbon(依然有人使用,还是很难替换掉)负载均衡+restTemplate实现rpc远程调用新版eureka依赖集成好了ribbon,可以不用重新导入consumer远程调用provider使用到了一个resttemplate类在消费者端的consumer中调用   @Resource   privateRestTemplaterestTemplate;/......
  • 国内外知名算法网站
     1. 国内算法网站对比网站名称国内/国外内容介绍题目难度题目数量题目类型竞赛活动解题思路编程工具LeetCode中国国内算法题库和面试题库,适合准备面试和提高算法能力合理分布,从Easy到Hard都有2000+算法和数据结构,涵盖多个领域和技术有,包括每周一次的周赛和不定期......
  • 【置顶】算法笔记目录
    1.图论dijkstra算法笔记2.树:树状数组算法笔记线段树算法笔记......
  • vue3 虚拟dom与diff算法
    diff算法vue3diff算法原码地址:  https://github.com/vuejs/core1.diff算法主要是说renderer.ts中patchChildren这段代码逻辑,如下:  2.diff算法排序分为无key时diff算法排序逻辑和有key时diff算法排序逻辑2.1无key时diff算法排序逻辑,分为三步如下,如图1中无key......
  • 自适应辛普森法积分算法
    引子有时候我们需要计算一个函数的定积分,粗略上可以使用估算的方法。如图所示,将原本的曲线粗略地看成一个梯形。这个方法叫梯形法制(TrapezoidalRule)。也叫做一阶牛顿-柯特斯闭型积分公式。其中所谓一阶,指的就是n=1的情况。最理想的情况就是把这个图像分割成无数个梯形......