luogu5282 【模板】快速阶乘算法

由于巨佬 shadowice1984 卡时限,本代码已经 T 请不要粘上去交

退役之后再写一个常数小的多项式取模吧

一句话题意:NP问题,求N!%P

吐槽:出题人太毒瘤...必须写任意模数NTT,而且加法取模还溢出...

我常数太大,粘的好久以前写的多项式取模,卡了卡常才A,大家1e3 1e4不要写vector,不要参考下面的代码

orz shadowice1984 写 \(O(\sqrt n\log n)\) 吊打我的 \(O(\sqrt n\log^2 n)\)

以下是 \(O(\sqrt n\log^2 n)\) 的题解

前置芝士: 多项式多点求值、多项式取模、多项式求逆

出门左转你谷模板区,包教不包会

前置芝士: 任意模数NTT

出门左转你谷模板区,包教不包会

本题题解

首先我们发现p是2^31-1的

你可以考虑像分段打表那样根号分块,把1~p分成 \(O(\sqrt p)\) 份

然后你求出每一份的值来,最后边角暴力就行了

那么怎么求呢

你会发现第一块是 \((1*2*...*s)\), 第二块是 \(((s+1)*(s+2)*...*(s+s))\)

第i块就是 \((s(i-1)+1)*(s(i-1)+2)*(s(i-1)+3)*(s(i-1)+s)\)

我们发现这是一个关于i的多项式,可以用分治+NTT在 \(O(\sqrt p \log^2p)\)的时间内求出这个多项式

然后你要求出第i=1...s的每一个数的值,也就是每一块数的积,你会发现是一个多项式多点求值,复杂度也是\(O(\sqrt p\log ^2p)\)

直接去隔壁模板区把多项式多点求值板子粘过来就行了

由于出题人故意卡模数,需要把FFT换成任意模数NTT...

然后你就在线A题了...

代码太丑,用vector xjb写的

#include <bits/stdc++.h>
using namespace std;

#define int long long

int n, p, s;
const int sb = 32768, sb2 = 1073741824;
const double pi = acos(-1);

int qpow(int x, int y)
{
    int res = 1;
    for (x %= p; y > 0; y >>= 1, x = x * (long long)x % p)
        if (y & 1) res = res * (long long)x % p;
    return res;
}

struct Complex { double real, imag; Complex(double r = 0, double i = 0) : real(r), imag(i) { } };
Complex a1[600000], a2[600000], b1[600000], b2[600000], a1b1[600000], ab[600000], a2b2[600000];
Complex operator+(const Complex &a, const Complex &b) { return Complex(a.real + b.real, a.imag + b.imag); }
Complex operator-(const Complex &a, const Complex &b) { return Complex(a.real - b.real, a.imag - b.imag); }
Complex operator*(const Complex &a, const Complex &b) { return Complex(a.real * b.real - a.imag * b.imag, a.real * b.imag + b.real * a.imag); }
Complex *w[22];
Complex getw(int x, int y, int falg) { return Complex(w[x][y].real, falg * w[x][y].imag); }
int *r[22];

void fftinit()
{
    for (int i = 0; i < 19; i++)
    {
        w[i] = new Complex[1 << i], r[i] = new int[1 << i];
        for (int j = 0; j < (1 << i); j++) w[i][j] = Complex(cos(pi * j / (1 << i)), sin(pi * j / (1 << i)));
        r[i][0] = 0;
        for (int j = 1; j < (1 << i); j++) r[i][j] = (r[i][j >> 1] >> 1) | ((j & 1) * (1 << (i - 1)));
    }
}

void fft(Complex *a, int len, int loglen, int falg)
{
    Complex w, t;
    for (int i = 0; i < len; i++) if (r[loglen][i] < i) swap(a[i], a[r[loglen][i]]);
    for (int i = 1, logi = 0; i < len; logi++, i <<= 1) for (int j = 0; j < len; j += i << 1) for (int k = 0; k < i; k++)
        w = getw(logi, k, falg), t = a[j + k + i] * w, a[j + k + i] = a[j + k] - t, a[j + k] = a[j + k] + t;
    if (falg == -1) for (int i = 0; i < len; i++) a[i].real /= len, a[i].imag /= len;
}

int toint(Complex x) { return (((long long)(round(x.real) + 0.5)) % p + p) % p; }

vector<int> operator*(vector<int> a, vector<int> b)
{
    int len = 1, loglen = 0; int sz = a.size() + b.size() - 1; while (len < sz) len <<= 1, loglen++;
    a.resize(len), b.resize(len);
    vector<int> res;
    for (int i = 0; i < len; i++) a1[i] = a[i] / sb, a2[i] = a[i] % sb, b1[i] = b[i] / sb, b2[i] = b[i] % sb;
    fft(a1, len, loglen, 1), fft(a2, len, loglen, 1), fft(b1, len, loglen, 1), fft(b2, len, loglen, 1);
    for (int i = 0; i < len; i++) a1b1[i] = a1[i] * b1[i], ab[i] = a1[i] * b2[i] + a2[i] * b1[i], a2b2[i] = a2[i] * b2[i];
    fft(a1b1, len, loglen, -1), fft(ab, len, loglen, -1), fft(a2b2, len, loglen, -1);
    for (int i = 0; i < len; i++)
        res.push_back(((toint(a1b1[i]) * (long long)sb2 % p + toint(ab[i]) * (long long)sb % p) % p + toint(a2b2[i])) % p);
    res.resize(sz);
    return res;
}

vector<int> operator+(vector<int> a, vector<int> b)
{
    vector<int> res; res.resize(max(a.size(), b.size()));
    a.resize(res.size()); b.resize(res.size());
    for (int i = 0; i < (int)res.size(); i++) res[i] = (a[i] + b[i]) % p;
    return res;
}

vector<int> operator-(vector<int> a, vector<int> b)
{
    vector<int> res; res.resize(max(a.size(), b.size()));
    a.resize(res.size()); b.resize(res.size());
    for (int i = 0; i < (int)res.size(); i++) res[i] = ((a[i] - b[i]) % p + p) % p;
    return res;
}

vector<int> poly_inv(vector<int> a)
{
    if (a.size() == 1) { a[0] = qpow(a[0], p - 2); return a; }
    int n = a.size(), newsz = (n + 1) >> 1;
    vector<int> b(a); b.resize(newsz); b = poly_inv(b);
    vector<int> c(a * b);
    for (int &i: c) i = (p - i) % p;
    c[0] = (c[0] + 2) % p; a = c * b; a.resize(n); return a;
}

// vector<int> poly_r(vector<int> a) { reverse(a.begin(), a.end());  return a; }

void div(vector<int> f, vector<int> g, vector<int> &q, vector<int> &r)
{
    int n = f.size() - 1, m = g.size() - 1;
    vector<int> gr = g; reverse(gr.begin(), gr.end());
    gr.resize(n - m + 1);
    q = f;
    reverse(q.begin(), q.end());
    q = q * poly_inv(gr);
    q.resize(n - m + 1);
    reverse(q.begin(), q.end());
    r = f - g * q;
    r.resize(m);
    // vector<int> gq = g * q;
    // r.resize(m);
    // gq.resize(m);
    // f.resize(m);
    // for (int i = 0; i < m; i++)
        // r[i] = ((f[i] - gq[i]) % p + p) % p;
}

vector<int> zz[200010];
int res[100010];

vector<int> fuck(int l, int r)
{
    if (l == r) { vector<int> res; res.push_back(l), res.push_back(s); return res; }
    int mid = (l + r) / 2;

    return fuck(l, mid) * fuck(mid + 1, r);
}

void prework(int x, int cl, int cr)
{
    if (cl == cr)
    {
        zz[x].push_back((p - cl) % p), zz[x].push_back(1);
        return;
    }
    int mid = (cl + cr) / 2;
    prework(x * 2, cl, mid), prework(x * 2 + 1, mid + 1, cr);
    zz[x] = zz[x * 2] * zz[x * 2 + 1];
}

void work(int x, int cl, int cr, vector<int> poly)
{
    if (cr - cl <= 400)
    {
        int sb = poly.size();
        for (int t = cl; t <= cr; t++)
        {
            int tmp = 1;
            for (int i = 0; i < sb; i++)
                res[t] = (res[t] + tmp * (long long)poly[i] % p) % p, tmp = tmp * (long long)t % p;
        }
        return;
    }
    vector<int> tmp, rel, rer;
    div(poly, zz[x * 2], tmp, rel);
    div(poly, zz[x * 2 + 1], tmp, rer);
    int mid = (cl + cr) / 2;
    work(x * 2, cl, mid, rel), work(x * 2 + 1, mid + 1, cr, rer);
}

signed main()
{
    fftinit();
    scanf("%lld%lld", &n, &p);
    // n = 998244353, p = 2147483647;
    s = sqrt(p) + 1;
    vector<int> poly = fuck(1, s);
    prework(1, 0, s);
    // printf("prework ok\n");
    work(1, 0, s, poly);
    // printf("work ok\n");
    int ans = 1;
    for (int i = n / s * s + 1; i <= n; i++) ans = ans * (long long)i % p;
    for (int i = 0; i < n / s; i++) ans = ans * (long long)res[i] % p;
    printf("%lld\n", ans);
    return 0; //拜拜程序~
}

原文地址:https://www.cnblogs.com/oier/p/10652775.html

时间: 2024-10-07 08:25:29

luogu5282 【模板】快速阶乘算法的相关文章

T4模板:MVC中用T4模板快速生成代码

T4模板快速生成代码: 以快速生Dal文件为例,下面为T4模板文件的内容 <#@ template debug="false" hostspecific="true" language="C#" #> <#@ include file="EF.Utility.CS.ttinclude"#> <#@ output extension=".cs" #> <# CodeG

JAVA算法4——连通性问题之路径压缩的加权快速合并算法

能否找到一个保证线性时间性能的算法,这个问题非常难.还有一些改进加权快速合并算法的简单方法.理想情况下,我们希望每个结点直接连到其树根,但又不想像快速合并算法那样改变大量连线.我们可以简单地把所检查的所有结点连到根上,从而接近理想情况.我们可以很容易地实现此方法,方法名为压缩路径,在合并操作时,经过每条路径就加一条连线,也就是把一路上遇到的对应于每个顶点的id数组值都设为连到树根上.净结果就是几乎完全把树变平坦了,逼近快速查找法所获得的理想状态. 还有其他许多方法来实现路径压缩下面程序实现路径压

HDU 2544 最短路(我的dijkstra算法模板、SPAFA算法模板)

思路:这道题是基础的最短路径算法,可以拿来试一下自己对3种方法的理解 dijkstra主要是从第一个点开始枚举,每次枚举出当当前最小的路径,然后再以那最小的路径点为起点,求出它到其它未标记点的最短距离 bellman-ford 算法则是假设有向网中有n 个顶点.且不存在负权值回路,从顶点v1 和到顶点v2 如果存在最短路径,则此路径最多有n-1 条边.这是因为如果路径上的边数超过了n-1 条时,必然会重复经过一个顶点,形成回路:而如果这个回路的权值总和为非负时,完全可以去掉这个回路,使得v1到v

Java 实现阶乘算法

阶乘算法如下: 以下列出 0 至 20 的阶乘: 0!=1,(0 的阶乘是存在的) 1!=1, 2!=2, 3!=6, 4!=24, 5!=120, 6!=720, 7!=5040, 8!=40320 9!=362880 10!=3628800 11!=39916800 12!=479001600 13!=6227020800 14!=87178291200 15!=1307674368000 16!=20922789888000 17!=355687428096000 18!=64023737

高维数据的快速最近邻算法FLANN

1.     简介 在计算机视觉和机器学习中,对于一个高维特征,找到训练数据中的最近邻计算代价是昂贵的.对于高维特征,目前来说最有效的方法是 the randomized k-d forest和the priority search k-means tree,而对于二值特征的匹配 multiple hierarchical clusteringtrees则比LSH方法更加有效. 目前来说,fast library for approximate nearest neighbors (FLANN)

模板实现查找算法

使用模版实现顺序查找和对分查找,遇到的问题: 1.class和typename的区别 声明模板参数时,class和typename关键字等价,可以互换:(早期的C++标准中,模版参数的关键字是通过class来标识的,后引入typename关键字.typename关键字本质上是标识一个类型,所以在模版参数定义时可以代替class.) 用作“嵌套依赖类型名”,必须用typename关键字标识:(例外:继承列表/成员初始化列表中的基类初始化时,可以不用typename标识“嵌套依赖类型名”,因为编译器

bzoj 3283 扩展BSGS + 快速阶乘

T2  扩展BSGS T3 快速阶乘 给定整数n,质数p和正整数c,求整数s和b,满足n! / pb = s mod pc 考虑每次取出floor(n/p)个p因子,然后将问题转化为子问题. 1 /************************************************************** 2 Problem: 3283 3 User: idy002 4 Language: C++ 5 Result: Accepted 6 Time:1704 ms 7 Memor

[转]快速平方根算法

在3D图形编程中,经常要求平方根或平方根的倒数,例如:求向量的长度或将向量归一化.C数学函数库中的sqrt具有理想的精度,但对于3D游戏程式来说速度太慢.我们希望能够在保证足够的精度的同时,进一步提高速度. Carmack在QUAKE3中使用了下面的算法,它第一次在公众场合出现的时候,几乎震住了所有的人.据说该算法其实并不是Carmack发明的,它真正的作者是Nvidia的Gary Tarolli(未经证实). // // 计算参数x的平方根的倒数 // float InvSqrt (float

快速模糊算法

前段时间在网上看到一个快速模糊算法,性能很不错. 源博客: http://www.lellansin.com/super-fast-blur-%E6%A8%A1%E7%B3%8A%E7%AE%97%E6%B3%95.html 博主对其进行了简单的bug修正以及性能优化. 在博主机子上使用该算法对一张5000x3000的图片进行模糊处理,仅需500-600毫秒,速度非常快. 代码如下: /* * Super Fast Blur v1.1+ * Original author: Mario Klin