Loading [MathJax]/jax/output/HTML-CSS/jax.js

类欧几里得算法能够用 logn的时间进行如下求和
ni=0ai+bc

我们首先记

f(a,b,c,n)=ni=0ai+bc

S2(n)=ni=1i=n(n+1)2

S3(n)=ni=1i2=n(n+1)(2n+1)6

ac||bc

f(a,b,c,n)=ni=0ai+bc=ni=0(ac×c+a%c)i+(bc×c+b%c)c=S2(n)×ac+(n+1)×bc+f(a%c,b%c,c,n)

a<c&&b<c,记 m=an+bc

f(a,b,c,n)=ni=0ai+bc=ni=0mj=1[ai+bcj]=ni=0m1j=0[ai+bcj+1](1)=ni=0m1j=0[aijc+cb](2)=ni=0m1j=0[ai>jc+cb1](3)=ni=0m1j=0[i>jc+cb1a](4)=m1j=0njc+cb1a=nmm1j=0jc+cb1a=nmf(c,cb1,a,m1)

这里我们解释一些细节

首先由式子 (1)>(2)为啥不能这样写?

=ni=0m1j=0[ai+bc>j](1)=ni=0m1j=0[ai>jcb](2)
我们可以假象一条数轴,整除 d这个操作,相当于将这条数轴切成一段一段的,每一段的优先级是一样的,并且每一段长度都为 d,而且每个数 n的优先级就是 nd,显然对于一个 n,它所在段的最小的数就是 nd×d,而 (1)式到 (2)式代表的意义就是将 j映射到它在整除 d意义下,第 j段的最小值,显然只要 ai+bc所在段 j即对答案有贡献,是与原式子等价的

(1)>(2)就咕咕咕了,当 j=ai+bc的时候,它显然对 (1)式是没有贡献的,而如果 c不整除 ai+b的时候,ai+b>jc是显然成立的,于是它又对 (2)式有贡献。

相似的方法,我们可以证明下面这个式子是错的

=ni=0m1j=0[aijc+cb](3)=ni=0m1j=0[ijc+cba](4)

读者可以自己证明

(tip: 若 aijc+cb在一个块里并且 jc+cbai,则 (3)无贡献,(4)有贡献)

(感觉这里自己理解复杂了啊?求更简单的理解方法 qwq)

然后你跑一遍递归就能求出来原式子了。

于是基础的类欧几里得算法就讲完了?

但是我们会止步于此吗?

(大雾)

咕咕咕,所以我们的模板还是没讲完 qwq

来我们把剩下两条式子也推一推

g(a,b,c,n)=ni=0iai+bc

h(a,b,c,n)=ni=0ai+bc2

其实手法是相似的呢,我们先求一遍 g(a,b,c,n)

ac||bc
g(a,b,c,n)=ni=0iai+bc=S3(n)×ac+S2(n)×bc+g(a%c,b%c,c,n)
a<c&&b<c,记 m=an+bc

g(a,b,c,n)=ni=0ai+bc=ni=0m1j=0i[i>jc+cb1a]=m1j=012(n+jc+cb1a+1)(njc+cb1a])=12m1j=0n2jc+cb1a2+njc+cb1a=12(n2mh(c,cb1,a,m1)+nmf(c,cb1,a,m1))=12[nm(n+1)h(c,cb1,a,m1)f(c,cb1,a,m1)]

你会震惊地发现,这个 g(a,b,c,n)竟然还和 h有关系,不过不用惊讶,我们等等推完 h你就知道怎么处理了

现在我们来求 h(a,b,c,n)

ac||bc
h(a,b,c,n)=ni=0ai+bc2=ni=0(ac×c+a%c)i+(bc×c+b%c)c2=ni=0(aci+bc+(a%c)i+(b%c)c)2=ni=0ac2i2+bc2+(a%c)i+(b%c)c2+2(acbci+ac(a%c)i+(b%c)ci+bc(a%c)i+(b%c)c)=S3(n)ac2+(n+1)bc2+h(a%c,b%c,c,n)+2S2(n)acbc+2g(a%c,b%c,c,n)ac+2f(a%c,b%c,c,n)bc

a<c&&b<c,记 m=an+bc
h(a,b,c,n)=ni=0ai+bc2 

等等,如果用相同的手法的话,我们不应该令 m=(an+bc)喵?但是酱紫做的话你会发现递归就不能保证 mn了,然后你就咕咕咕了。

我们可以采用求和降次的手法 (就是 n次多项式的 n项求和为 n+1次,我们只需要多一次枚举就能降次)

在这题我们用的是

(2ni=1i)n=n2

代入有

h(a,b,c,n)=ni=0ai+bc2=ni=0(2mj=1j)ai+bc=ni=0(2m1j=0j+1)ai+bc=2m1j=0(j+1)ni=0[ai+bcj+1]ni=0ai+bc=2m1j=0(j+1)ni=0[aijc+cb]f(a,b,c,n)=2m1j=0(j+1)ni=0[i> jc+cb1a]f(a,b,c,n)=2m1j=0(j+1)(njc+cb1a)f(a,b,c,n)=2m1j=0nj+njjc+cb1ajc+cb1af(a,b,c,n)=2nS2(m1)+2nm2g(c,cb1,a,m1)2f(c,cb1,a,m1)f(a,b,c,n)=2nS2(m)2g(c,cb1,a,m1)2f(c,cb1,a,m1)f(a,b,c,n)

然后我们的式子就推完了,在实现方面,因为 gh都有互相推导的关系,所以我们可以存一个结构体,在结构体里把三个函数算出来然后递归做

具体实现看代码吧 qwq

#include<bits/stdc++.h>
#define fo(i, a, b) for (Re int i = (a); i <= (b); ++i)
#define fd(i, a, b) for (Re int i = (a); i >= (b); --i)
#define edge(i, u) for (Re int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
#define Re register
#define pb push_back
#define F first
#define S second
#define ll long long
#define lowbit(x) (x & -x)
#define mod 998244353
#define N 15
#define inv2 499122177
#define inv6 166374059
ll n, a, b, c, T;
struct node{
    ll f, g, h;
};
inline ll S2 (ll x) {return (x * (x + 1) >> 1) % mod;}
inline ll S3 (ll x) {return x * (x + 1) % mod * (x << 1 | 1) % mod * inv6 % mod;}
inline ll add (ll x, ll ad) {return (x + ad) % mod;}
inline ll sqr (ll x) {return x * x % mod;}
inline node calc (ll a, ll b, ll c, ll n)
{
    node ret;
    if (a == 0)
    {
        ret.f = (b / c) * (n + 1) % mod;
        ret.g = S2(n) * (b / c) % mod;
        ret.h = (b / c) * ret.f % mod;
//        printf("1:%d %d %d %d    %d %d %d\n", a, b, c, n, ret.f, ret.g, ret.h);
        return ret;
    }
    if (a >= c || b >= c)
    {
        node tmp = calc(a % c, b % c, c, n);
        ll ac = a / c, bc = b / c;
        ret.f = add(add(tmp.f, ac * S2(n) % mod), bc * (n + 1) % mod);
        ret.g = add(add(tmp.g, ac * S3(n) % mod), bc * S2(n) % mod);
        ret.h = add(add(add(add(add(tmp.h, sqr(ac) * S3(n) % mod), sqr(bc) * (n + 1) % mod), 2 * ac * bc % mod * S2(n) % mod), 2 * ac * tmp.g % mod), 2 * bc * tmp.f % mod);
 //       printf("2:%d %d %d %d    %d %d %d\n", a, b, c, n, ret.f, ret.g, ret.h);
        return ret;
    }
    ll m = (a * n + b) / c;
    node tmp = calc(c, c - b - 1, a, m - 1);
    ret.f = add(n * m % mod, -tmp.f);
    ret.g = add(n * m % mod * (n + 1) % mod, -tmp.h - tmp.f) * inv2 % mod;
    ret.h = add(n * m % mod * (m + 1) % mod, -2 * (tmp.f + tmp.g) - ret.f);
 //       printf("3:%d %d %d %d    %d %d %d\n", a, b, c, n, ret.f, ret.g, ret.h);
    return ret;
}
int main ()
{
    scanf("%lld", &T);
    while (T--)
    {
        scanf("%lld %lld %lld %lld", &n, &a, &b, &c);
        node ans = calc(a, b, c, n);
        printf("%lld %lld %lld\n", (ans.f + mod) % mod, (ans.h + mod) % mod, (ans.g + mod) % mod);
    }
    return 0;
}
C++

由于这篇 blog 的公式太多了,如果有错的话请在评论处指出谢谢 qwq

分类: 文章

HomuraCat

明日は何になる? やがて君になる!

2 条评论

Remmina · 2019年1月29日 1:34 下午

佩服 qhy 啊,能用这破 MarkDown 编辑器里写这么长的公式 QwQ

    quhengyi11 · 2019年1月29日 1:51 下午

    我在 moeditor 里面编辑然后复制过来的 qwq

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注