zoukankan      html  css  js  c++  java
  • 类欧几里德算法

    oi-wiki:类欧几里德算法

    经典模型

    [f(a,b,c,n)=sum_{i=0}^n lfloor frac{ai+b}{c} floor ]

    (a,b,c,n le 10^9,c ot= 0),要求在 (log n) 的时间内求出。

    约定

    为了方便表示,做以下规定:

    (x/y) 表示 (lfloor frac{a}{b} floor)

    (a' = a mod c)

    情况一

    [a,b ge c ]

    这个时候我们可以将 (a,b) 中大于 (c) 的部分拿出来,以简化问题:

    [egin{aligned} f(a,b,c,n ) &= sum_{i=0}^n lfloor frac{ai+b}{c} floor\ &= frac{n imes (n + 1) imes a/c}{2} + (n+1) * (b/c)+ sum_{i = 0}^n lfloor frac{a'i+b'}{c} floor\ &= frac{n imes (n + 1) imes a/c}{2} + (n+1) * (b/c)+ f(a',b',c,n) end{aligned} ]

    情况二

    [a,b < c ]

    这时候我们需要一些 trick 来想方设法地交换求和号:

    [egin{aligned} f(a,b,c,n) &= sum_{i=0}^n lfloor frac{ai+b}{c} floor\ &= sum_{i=0}^n sum_{j=0}^{(ai+b)/c-1}1\ &= sum_{j=0}^{(an+b)/c-1}sum_{i=0}^n[j<(ai+b)/c] end{aligned} ]

    那个 (j < (ai+b)/c) 不是很好搞,但是如果关于底和顶的基本功比较好的话应该是问题不大的。

    先抛俩较为基础的结论:((n) 为整数,(x) 为实数)

    [n le x Leftrightarrow n le lfloor x floor ]

    [n > x Leftrightarrow n > lfloor x floor ]

    然后推:

    [j < (ai+b)/c\ j+1 le (ai+b)/c\ j+1 le frac{ai+b}{c}\ cj+c le ai+b\ cj+c-b le ai\ cj+c-b-1 < ai\ i > (cj+c-b-1)/a ]

    然后原式子就好看很多了。定义 (m = (an+b)/c),然后:

    [egin{aligned} f(a,b,c,n) &= sum_{j=0}^{(an+b)/c-1}sum_{i=0}^n[j<(ai+b)/c]\ &= sum_{j=0}^{m-1}n-lfloor frac{cj+c-b-1}{a} floor\ &= nm-f(c,c-b-1,a,n) end{aligned} ]

    感觉没啥用?其实是有用的,观察到 (a,c) 换了位置,而情况一可以将 (a)(c),因此复杂度和辗转相除法的复杂度一样,都是 (log n)

    情况三(边界)

    [a=0 ]

    [f(a,b,c,n)=(n+1)lfloor frac{b}{c} floor ]

    拓展

    现在又跑出来俩类似 (f) 的函数 (g)(h)(h) 是二次的 (f)(g)(h) 的辅助数组:

    [g(a,b,c,n) = sum_{i = 0}^n i lfloor frac{ai+b}{c} floor\ h(a,b,c,n) = sum_{i=0}^n lfloor frac{ai+b}c floor ^2 ]

    这俩式子的推导与 (f) 的思想类似,都是分为三种情况,第一种取模,第二种先办法搞出求和号然后交换以实现 (a,c) 互换位置的效果,第三种边界。第三种太水了,就不写了。

    g函数

    情况一

    [egin{aligned} g(a,b,c,n) &= sum_{i=0}^n i lfloor frac{ai+b}c floor\ &= sum_{i=0}^n i(i imes a/c + b/c) lfloor frac{a'i+b'}c floor \ &= a/c imes n(n+1)(2n+1)/6 + b/c imes n(n+1)/2 + g(a',b',c,n) end{aligned} ]

    情况二

    [egin{aligned} g(a,b,c,n) &= sum_{i=0}^n i sum_{j=0}^{(ai+b)/c-1}1\ &= sum_{j=0}^{m-1}sum_{i=0}^ni[j < (ai+b)/c]\ &= sum_{j=0}^{m-1}n(n+1)/2 - (jc+c-b-1)/a imes ((jc+c-b-1)/a-1)/2\ &= frac{1}{2}(mn(n+1)-h(c,c-b-1,a,m-1)-f(c,c-b-1,a,m-1)) end{aligned} ]

    h函数

    情况一

    公式太难打了,就直接给结论吧。反正推导全是暴力拆式子

    [h(a,b,c,n) = \ small (a/c)^2n(n+1)(2n+1)/6 + (a/c)(b/c)n(n+1)+(n+1)(b/c)^2+2(a/c)g(a',b',c,n)+2(b/c)f(a',b',c,n)+h(a',b',c,n) ]

    情况二

    这里还需要一个小 trick:(普通幂表示成自然数幂和)

    [n^2 = (2sum_{i=0}^{n-1}i) + n ]

    然后就可以推了:

    [egin{aligned} h(a,b,c,n) &= sum_{i=0}^n 2 sum_{j=0}^{(ai+b)/c-1}j + f(a,b,c,n)\ &= 2 sum_{j=0}^{m-1}j sum_{i=0}^n[(ai+ b)/c > j] + f(a,b,c,n)\ &= 2sum_{j=0}^{m-1}j(n-lfloor frac{cj+c-b-1}a floor)+f(a,b,c,n)\ &= nm(m-1) - 2g(c,c-b-1,a,m-1)+f(a,b,c,n) end{aligned} ]

    由于 (f)(g)(h) 需要相互调用,我们可以每次打包返回三个值。

    题目与模板

    例题:P5170 【模板】类欧几里得算法

    类欧模板(调试用)

    struct node {
    	ll f, g, h;
    	node() {}
    	node(ll a, ll b, ll c) { f = a, g = b, h = c; }
    };
    node sol(int a, int b, int c, int n) {
    	ll nwf = 0, nwg = 0, nwh = 0;
    	if (!a) {
    		nwf = (b / c) * (n + 1) % P;
    		nwg = (b / c) * n % P * (n + 1) % P * inv2 % P;
    		nwh = (b / c) * (b / c) % P * (n + 1) % P;
    		return node(nwf, nwg, nwh);
    	}
    	if (a >= c || b >= c) {
    		node nd = sol(a % c, b % c, c, n);
    		ll tf = nd.f, tg = nd.g, th = nd.h;
    		nwf = ((a / c) * n % P * (n + 1) % P * inv2 + (b / c) * (n + 1) + tf) % P;//bug
    		nwg = ((a / c) * n % P * (n + 1) % P * (2ll * n + 1) % P * inv6 + (b / c) * n % P * (n + 1) % P * inv2 + tg) % P;
    		nwh = (a / c) * (a / c) % P * n % P * (n + 1) % P * (2ll * n + 1) % P * inv6;
    		nwh = (nwh + (a / c) * (b / c) % P * n % P * (n + 1) + (n + 1) * (b / c) % P * (b / c)) % P;
    		nwh = (nwh + 2ll * (b / c) * tf + 2ll * (a / c) * tg + th) % P;
    		return node(nwf, nwg, nwh);
    	}
    	ll m = (a * n + b) / c;//bug
    	node nd = sol(c, c - b - 1, a, m - 1);
    	m %= P;
    	ll tf = nd.f, tg = nd.g, th = nd.h;
    	nwf = (m * n - tf) % P;
    	nwg = (m * n % P * (n + 1) - th - tf) % P * inv2 % P;
    	nwh = (n * m % P * (m - 1) - 2ll * tg + nwf) % P;
    	return node(nwf, nwg, nwh);
    }
    

    注意一下取模的问题,计算答案要取模,但是向下递归的 (m) 不能扔个 (m mod P) 进去。

  • 相关阅读:
    SQL Union 和Union All 的区别
    SqlHelper.cs
    转载WPF:创建你的第一个WPF项目
    数据库分页存储过程
    sql 常用基础查询
    创建表--自动编号字段且自增
    模糊查询
    模式的定义
    C#计算两个日期之间相差的天数
    优化SQL查询:如何写出高性能SQL语句
  • 原文地址:https://www.cnblogs.com/JiaZP/p/13857028.html
Copyright © 2011-2022 走看看