Processing math: 100%

用途

用于在 O(n23)的时间复杂度内求出 ni=1f(i),其中 f为积性函数

原理

s(i)=ni=1f(i)

构造积性函数 g(i),设 fg的狄利克雷卷积为 fg

即:

(fg)(i)=d|ig(d)f(id)

对卷积得到的函数求前缀和:

ni=1(fg)(i)=ni=1d|ig(d)f(id)=nd=1g(d)d|if(id)=nd=1g(d)ndi=1f(i)=nd=1g(d)s(nd)

移一下项就有:

g(1)s(n)=ni=1(fg)(i)nd=2g(d)s(nd)

很明显这个东西就能用记忆化搜索去求 s(n)了,后面那个 nd=2g(d)s(nd)用数论分块就行了

这个构造的 g要求比较高,你需要让 nd=2g(d)很好求,同时需要让 ni=1(fg)(i)很好求这是个令人头疼的事情

复杂度

然而我不会算,所以千万别问我怎么算的

根据主定理:

T(n)=O(n)+ni=1T(i)+T(ni)=ni=1O(i)+O(ni)=O(n34)

如果我们先用线性筛来预处理出 i[1,k]内的 s(i),那么记忆化搜索的复杂度为 O(nk),预处理复杂度为 O(k),因此 k=n23时取到最优复杂度,为 O(n23)

实现

一般实现的时候用 map 来进行记忆化就行了,因为需要记忆化的元素个数为 n13,一般不会超过 104(否则 n23就已经超过 108的级别了),带个小 log问题不大

如果用哈希表应该会更快,但是没试过

当然也有更好的记忆化方法,就是对于 i记忆化 s(ni)的值,i的取值范围为 [1,n13]

这是因为你每次都会去搜当前的数字除以一个数向下取整

nab=nab

还有当 in时有 nni=i

所以这样表示状态是不会出错的

例题

Sum BZOJ – 3944

求欧拉函数 φ和莫比乌斯函数 μ的前缀和,n2311

经典的杜教筛模板题

对于两者,都构造 g(i)=1

因为:

(φg)(i)=i
(μg)(i)=[i=1]

非常愉快

但这题似乎有点卡常(BZOJ 算的是总时限另当别论),有这么几个优化技巧:

  • 尽量 unsigned int 存,避免 long long
  • μ的前缀和用 int
  • 不需要进行两次记忆化搜索,因为两棵 dfs 树是一模一样的,在同一个 dfs 里同时计算两个函数值就行了
  • 由于有多组询问,可以适当地增大预处理的数据量

代码:

#include <bits/stdc++.h>

#define PS (2000000)
#define SS (1075)

using namespace std;

typedef long long LL;
typedef unsigned int UI;

template<typename _Tp> inline void IN(_Tp &dig)
{
    char c; bool flag = 0; dig = 0;
    while (c = getchar(), !isdigit(c)) if (c == '-') flag = 1;
    while (isdigit(c)) dig = dig * 10 + c - '0', c = getchar();
    if (flag) dig = -dig;
}

int testcase, p[PS / 10], mp[PS], ms[SS];

UI n;

LL pp[PS], ps[SS];

bool book[PS], vis[SS];

void preWork()
{
    pp[1] = mp[1] = 1;
    for (int i = 2; i < PS; i += 1)
    {
        if (!book[i]) p[++p[0]] = i, pp[i] = i - 1, mp[i] = -1;
        for (int j = 1, k; j <= p[0]; j += 1)
        {
            k = i * p[j];
            if (k >= PS) break;
            book[k] = 1;
            if (i % p[j]) pp[k] = pp[i] * pp[p[j]], mp[k] = mp[i] * mp[p[j]];
            else { pp[k] = pp[i] * p[j], mp[k] = 0; break; }
        }
    }
    for (int i = 2; i < PS; i += 1) pp[i] += pp[i - 1], mp[i] += mp[i - 1];
}

LL phi;

int mu;

void dfs(UI x)
{
    if (x < PS) { phi = pp[x], mu = mp[x]; return; }
    LL &pf = ps[n / x]; int &mf = ms[n / x];
    if (vis[n / x]) { phi = ps[n / x]; mu = ms[n / x]; return; }
    vis[n / x] = 1, pf = (1ll * x * (1 + x)) >> 1, mf = 1;
    for (UI i = 2, j; i <= x; i = j + 1)
    {
        j = x / (x / i), dfs(x / i);
        pf -= (j - i + 1) * phi, mf -= (j - i + 1) * mu;
    }
    phi = pf, mu = mf;
}

int main(int argc, char const* argv[])
{
    IN(testcase), preWork();
    while (testcase--)
    {
        IN(n), memset(vis, 0, sizeof(vis)), dfs(n);
        printf("%lld %d\n", phi, mu);
    }
    return 0;
}
C++
分类: 文章

Remmina

No puzzle that couldn't be solved.

0 条评论

发表回复

Avatar placeholder

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