BZOJ 1919 [Ctsc2010]性能优化

题目:
http://www.lydsy.com/JudgeOnline/problem.php?id=1919

题意:
给出两个长度为 n 的整数序列a[0..n1],b[0..n1]和非负整数 C
对于两个长度为n的整数序列,定义 运算,结果为一个长度为n的整数序列,例如 fg=h ,则有 h[k]=i+jk(modn)f[i]g[j]
abbb 每一位模 (n+1) 的值,其中有 C 运算, (n+1) 是质数, n 的质因数大小均不超过10
n5105,a[i],b[i],C109

题解:
由原根的性质可知,长度为 n 的FFT即可支持运算,难点在于 bC 使得值域过大,即使能够快速计算长度为 n 的FFT,使用复数运算的FFT也很难得到精确的答案。

先考虑如何快速计算长度为n的FFT。
n=2k 时,FFT每次是将序列一分为二,然后利用分治的技巧来进行合并。
因此当 n=2k13k25k37k4 时,FFT每次可能将序列一分为 p(p=2,3,5,7) ,合并时的式子需要重新推导。
不妨设是将 p 个长度为n的式子合并成一个长度为 pn 的式子,即利用 p n个点值得到 pn 个点值。
由于分裂时将模 p 意义相同的部分放在了一起,所以对于合并后的多项式

F(x)=0i<pnaixi

拆分的 p 个多项式分别为
Fr(x)=0i<naip+rxi

故有

F(ωan+bpn)=0r<p(ωan+bpn)rFr(ωbn)

于是可以 O(p) 合并出每个点的值,而这样的分治层数是 O(4i=1ki)=O(logn) ,每层的复杂度是 O(pn)=O(7n) ,因此整体的复杂度是 O(nlogn)
上述方法也可非递归实现,在分裂过程中注意每段之间互不影响,在合并过程中注意存储方式即可,笔者的做法就是迭代的做法。

再考虑解决精度问题,由同余关系的性质,可以使得每次计算相乘时的值域降低到 O(n2) ,但需要将单位复根映射到模意义下的剩余系中。
由于 (n+1) 是质数, φ(n+1)=n ,所以在模 (n+1) 意义下存在原根 g ,使得gωn(modn+1),于是利用NTT代替FFT计算即可。
由于模 (n+1) 意义下原根的数量为 φ(n)=npiisprime,pi|n11pi ,而 n 的质因子大小不超过10,所以期望检查3585次就可以找到原根了。

上述做法基于 n 10-smooth number,即Cooley–Tukey FFT algorithm,而对于更强性质的n,可以使用Bluestein’s algorithm

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 500001;
int n, m, mod, tot, p[maxn], pw[maxn], a[maxn], b[maxn];
int mod_pow(int x, int k)
{
    int ret = 1;
    for( ; k > 0; k >>= 1, x = (LL)x * x % mod)
        if(k & 1)
            ret = (LL)ret * x % mod;
    return ret;
}
void NTT(int x[maxn], int flag)
{
    // go deeper
    static int y[maxn] = {};
    int *cur = x, *nxt = y;
    for(int i = tot - 1, delta = n / p[i]; i > 0; --i, delta /= p[i], swap(cur, nxt))
        for(int j = 0, *np = nxt; j < n; j += delta * p[i])
            for(int k = 0; k < p[i]; ++k)
                for(int l = 0, *cp = cur + j + k; l < delta; ++l, ++np, cp += p[i])
                    *np = *cp;
    // recursion
    for(int i = 0, clen = 1, nlen = p[i]; i < tot; ++i, clen = nlen, nlen *= p[i], swap(cur, nxt))
        for(int j = 0, k = 0, ww = 1, delta = 0; j < n; ++j, k = k + 1 < clen ? k + 1 : 0, ww = (LL)ww * pw[i] % mod, delta = delta + nlen > j ? delta : delta + nlen)
        {
            nxt[j] = 0;
            for(int t = 0, www = 1; t < nlen; t += clen, www = (LL)www * ww % mod)
                nxt[j] = (nxt[j] + (LL)www * cur[delta + t + k]) % mod;
        }
    if(flag == -1)
    {
        reverse(cur + 1, cur + n);
        for(int i = 0; i < n; ++i)
            cur[i] = (LL)cur[i] * n % mod; // n * n mod (n + 1) = 1
    }
    if(cur != x)
        memcpy(x, cur, n * sizeof(int));
}
int main()
{
    int tmp;
    scanf("%d%d", &n, &m);
    mod = n + 1;
    tmp = n;
    m = (m - 1) % n + 1;
    for(int i = 2; i * i <= tmp; ++i)
        for( ; tmp % i == 0; tmp /= i, p[tot++] = i);
    if(tmp > 1)
        p[tot++] = tmp;
    for(int ori = 2; ; ++ori)
    {
        bool flag = 1;
        for(int i = 0; i < tot && flag; ++i)
            if(!i || p[i - 1] != p[i])
                flag &= mod_pow(ori, n / p[i]) != 1;
        if(flag)
        {
            pw[tot - 1] = ori;
            for(int i = tot - 2; i >= 0; --i)
                pw[i] = mod_pow(pw[i + 1], p[i + 1]);
            break;
        }
    }
    for(int i = 0; i < n; ++i)
        scanf("%d", a + i);
    NTT(a, 1);
    for(int i = 0; i < n; ++i)
        scanf("%d", b + i);
    NTT(b, 1);
    for(int i = 0; i < n; ++i)
        a[i] = (LL)a[i] * mod_pow(b[i], m) % mod;
    NTT(a, -1);
    for(int i = 0; i < n; ++i)
        printf("%d\n", a[i]);
    return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值