数学——多项式与计数

We must know . We will know. ----David Hilbert

 

To Do List

1.优化多项式板子

1.5增加多项式板子功能

2.学习FWT(快速沃尔什变换)

3.学习FMT(快速莫比乌斯变换)

4.学习计算几何以及BSGS的应用

 

板子

多项式板子(旧版)
 int rev[N];//NTT/FFT反转二进制
namespace MTT {//任意模数多项式乘法
    const double PI = acos((double)-1);
    struct Cp {
        double x, y;
        Cp() { ; }
        Cp(double _x, double _y) : x(_x), y(_y) { }
        inline Cp operator + (const Cp& t) const { return (Cp) { x + t.x, y + t.y }; }
        inline Cp operator - (const Cp& t) const { return (Cp) { x - t.x, y - t.y }; }
        inline Cp operator * (const Cp& t) const { return (Cp) { x* t.x - y * t.y, x* t.y + y * t.x }; }
    }A[N], B[N], C[N], w[N / 2];

#define E(x) ll(x+0.5)%P

    void FFT(int n, Cp* a, int f) {
        for (int i = 0;i <= n - 1;i++) if (rev[i] < i) swap(a[i], a[rev[i]]);
        w[0] = Cp(1, 0);
        for (int i = 1;i < n;i <<= 1) {
            Cp t = Cp(cos(PI / i), f * sin(PI / i));
            for (int j = i - 2;j >= 0;j -= 2) w[j + 1] = t * (w[j] = w[j >> 1]);
            for (int l = 0;l < n;l += 2 * i) {
                for (int j = l;j < l + i;j++) {
                    Cp t = a[j + i] * w[j - l];
                    a[j + i] = a[j] - t;
                    a[j] = a[j] + t;
                }
            }
        }
        if (f == -1) for (int i = 0;i <= n - 1;i++) a[i].x /= n, a[i].y /= n;
    }

    void Multiply(vector<int> a, vector<int> b, vector<int>& res, int P) {
        // [0,n-1]*[0,m-1]->[0,n+m-2]
        int n = a.size(), m = b.size();res.resize(n + m - 1);
        int S = (1 << 15) - 1;

        int R = 1, cc = -1;
        while (R <= n + m - 1) R <<= 1, cc++;
        for (int i = 1;i <= R;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << cc);
        for (int i = 0;i <= n - 1;i++) A[i] = Cp((a[i] & S), (a[i] >> 15));
        for (int i = 0;i <= m - 1;i++) B[i] = Cp((b[i] & S), (b[i] >> 15));
        for (int i = n;i <= R - 1;i++) A[i] = Cp(0, 0);
        for (int i = m;i <= R - 1;i++) B[i] = Cp(0, 0);

        FFT(R, A, 1), FFT(R, B, 1);
        for (int i = 0;i <= R - 1;i++) {
            int j = (R - i) % R;
            C[i] = Cp((A[i].x + A[j].x) / 2, (A[i].y - A[j].y) / 2) * B[i];
            B[i] = Cp((A[i].y + A[j].y) / 2, (A[j].x - A[i].x) / 2) * B[i];
        }
        FFT(R, C, -1), FFT(R, B, -1);
        for (int i = 0;i <= n + m - 2;i++) {
            ll a = E(C[i].x), b = E(C[i].y), c = E(B[i].x), d = E(B[i].y);
            res[i] = (a + ((b + c) << 15) + (d << 30)) % P;
        }
    }
    // void Multiply_db(vector<double> a, vector<double> b, vector<double>& res) {//答案double类型
    //     // [0,n-1]*[0,m-1]->[0,n+m-2]
    //     int n = a.size(), m = b.size();res.resize(n + m - 1);

    //     int R = 1, cc = -1;
    //     while (R <= n + m - 1) R <<= 1, cc++;
    //     for (int i = 1;i <= R;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << cc);
    //     for (int i = 0;i <= n - 1;i++) A[i] = Cp(a[i], 0);
    //     for (int i = 0;i <= m - 1;i++) B[i] = Cp(b[i], 0);
    //     for (int i = n;i <= R - 1;i++) A[i] = Cp(0, 0);
    //     for (int i = m;i <= R - 1;i++) B[i] = Cp(0, 0);

    //     FFT(R, A, 1), FFT(R, B, 1);
    //     for (int i = 0;i < R;i++) C[i] = A[i] * B[i];
    //     FFT(R, C, -1);
    //     for (int i = 0;i <= n + m - 2;i++) res[i] = C[i].x;
    // }

#undef E
}


int _inv[N];
void Poly_init(int mod = MOD) {
    _inv[1] = 1;
    for (int i = 2;i < N;i++) _inv[i] = 1llu * _inv[MOD % i] * (MOD - MOD / i) % MOD;
}

ll qp(ll a, ll n, int mod = MOD) {
    a %= mod;
    ll res = 1;
    while (n) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}
const int G = 3;
const int inv_G = qp(G, MOD - 2, MOD);//332748118
int init_NTT(int deg) {//a*b的多项式最高项.
    int lim = 1;for (lim = 1;lim <= deg;lim <<= 1);return lim;
}
void NTT(vector<int>& a, int n, int op) {//op=1为正变换,-1为逆变换
    a.resize(n);
    //reverse
    for (int i = 0;i < n;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
    for (int i = 0;i < n;i++) if (i < rev[i]) swap(a[i], a[rev[i]]);

    for (int m = 2;m <= n;m <<= 1) {
        int g1 = qp(op == 1 ? G : inv_G, (MOD - 1) / m, MOD);
        for (int i = 0;i < n;i += m) {
            int gk = 1;
            for (int j = 0;j < m / 2;j++) {
                int x = a[i + j], y = 1ll * a[i + j + m / 2] * gk % MOD;
                a[i + j] = (x + y) % MOD;a[i + j + m / 2] = (x - y + MOD) % MOD;gk = 1ll * gk * g1 % MOD;
            }
        }
    }
    int inv_n = qp(n, MOD - 2, MOD);
    if (op == -1) for (int i = 0;i < n;i++) a[i] = 1ll * a[i] * inv_n % MOD;
}
struct Poly {
    vector<int> p;
    Poly(int n) { p.resize(n); }
    Poly friend operator*(Poly a, Poly b) {//MOD=998244353,..
        int sa = a.p.size() - 1, sb = b.p.size() - 1;int deg = sa + sb;
        int lim = init_NTT(deg);//degree最高项系数
        NTT(a.p, lim, 1);NTT(b.p, lim, 1);
        for (int i = 0;i < lim;i++) a.p[i] = 1ll * a.p[i] * b.p[i] % MOD;
        NTT(a.p, lim, -1);
        a.p.resize(deg + 1);
        return a;
    }
    // Poly friend operator*(Poly a, Poly b) {//任意模数多项式乘法
    //     int sa = a.p.size(), sb = b.p.size();
    //     Poly res(sa + sb - 1);
    //     MTT::Multiply(a.p, b.p, res.p, MOD);
    //     res.p.resize(sa + sb - 1);
    //     return res;
    // }
    // Poly friend operator*(Poly a, Poly b) {//答案double类型||如果答案不取模,改成longdouble即可
    //     int sa = a.p.size(), sb = b.p.size();
    //     Poly res(sa + sb - 1);
    //     MTT::Multiply_db(a.p, b.p, res.p);
    //     res.p.resize(sa + sb - 1);
    //     return res;
    // }
    Poly friend operator*(int k, Poly a) {
        for (int i = 0;i < a.p.size();i++) a.p[i] = 1ll * a.p[i] * k % MOD;
        return a;
    }
};
Poly Dev(Poly a) {//多项式求导
    int n = a.p.size();
    for (int i = 1;i < n;i++) a.p[i - 1] = 1ll * a.p[i] * i % MOD;
    return a.p.back() = 0, a;
}
Poly Int(Poly a) {//多项式求积分
    int n = a.p.size();
    for (int i = n - 1;i >= 0;i--) a.p[i] = 1ll * a.p[i - 1] * _inv[i] % MOD;//预处理逆元降低复杂度
    //for (int i = n - 1;i >= 0;i--) a.p[i] = a.p[i - 1] * qp(i, MOD - 2, MOD) % MOD;//直接求逆元
    return a.p[0] = 0, a;
}
Poly Inv(Poly a) {//多项式乘法逆元
    if (a.p.size() == 1) return a.p[0] = qp(a.p[0], MOD - 2, MOD), a;
    const int len = a.p.size();
    Poly ta = a;ta.p.resize((len + 1) >> 1);
    Poly b = Inv(ta);b.p.resize(len);
    int lim = init_NTT(len << 1);
    NTT(a.p, lim, 1);NTT(b.p, lim, 1);
    for (int i = 0;i < lim;i++) b.p[i] = 1ll * b.p[i] * (MOD + 2 - 1ll * a.p[i] * b.p[i] % MOD) % MOD;
    NTT(b.p, lim, -1);b.p.resize(len);
    return b;
}
Poly __Inv(Poly a) {//任意模数多项式乘法逆元
    if (a.p.size() == 1) return a.p[0] = qp(a.p[0], MOD - 2, MOD), a;
    const int len = a.p.size();
    Poly ta = a;ta.p.resize((len + 1) >> 1);
    Poly b = __Inv(ta);b.p.resize(len);
    Poly c(1), d(1);
    MTT::Multiply(a.p, b.p, c.p, MOD);c.p.resize(len);
    MTT::Multiply(c.p, b.p, d.p, MOD);d.p.resize(len);
    for (int i = 0;i < len;i++) b.p[i] = (b.p[i] + b.p[i]) % MOD;
    for (int i = 0;i < len;i++) b.p[i] = (MOD + b.p[i] - d.p[i]) % MOD;
    b.p.resize(len);
    return b;
}
void Div(Poly& F, Poly& G, Poly& R, Poly& Q) {//多项式除法,给定多项式F,G 求出多项式R,Q 使得F=G*Q+R
    int n = F.p.size() - 1;int m = G.p.size() - 1;
    Q.p.resize(n - m + 1);R.p.resize(m);
    Poly Fr(n + 1);for (int i = 0; i <= n; i++) Fr.p[n - i] = F.p[i];
    Poly Gr(m + 1);for (int i = 0; i <= m; i++) Gr.p[m - i] = G.p[i];
    Gr.p.resize(n - m + 1);Fr.p.resize(n - m + 1);
    Gr = Inv(Gr);Poly t = Gr * Fr;
    for (int i = 0;i <= n - m;i++) Q.p[i] = t.p[n - m - i];
    t = G * Q;
    for (int i = 0;i < m;i++) R.p[i] = (MOD + F.p[i] - t.p[i]) % MOD;
}
//保证[x ^ 0]f(x) = 1
Poly Ln(Poly a) {//多项式对数
    Poly ta = Dev(a);
    ta = ta * Inv(a);
    ta = Int(ta);
    return ta.p.resize(a.p.size()), ta;
}
//保证[x ^ 0]f(x) = 0
Poly Exp(Poly a) {//多项式指数
    if (a.p.size() == 1) return a.p[0] = 1, a;
    const int len = a.p.size();
    Poly ta = a;ta.p.resize((len + 1) >> 1);
    Poly b = Exp(ta);b.p.resize(len);
    Poly ln_b = Ln(b);ln_b.p.resize(len);
    int lim = init_NTT(len << 1);
    NTT(a.p, lim, 1);NTT(b.p, lim, 1);NTT(ln_b.p, lim, 1);
    for (int i = 0;i < lim;i++) b.p[i] = 1ll * b.p[i] * (MOD + 1 - ln_b.p[i] + a.p[i]) % MOD;
    NTT(b.p, lim, -1);b.p.resize(len);
    return b;
}
Poly __Exp(Poly a) {//任意模数多项式指数
    if (a.p.size() == 1) return a.p[0] = 1, a;
    const int len = a.p.size();
    Poly ta = a;ta.p.resize((len + 1) >> 1);
    Poly b = __Exp(ta);b.p.resize(len);
    Poly ln_b = Ln(b);ln_b.p.resize(len);
    Poly c(1), d(1);
    MTT::Multiply(b.p, ln_b.p, c.p, MOD);c.p.resize(len);
    MTT::Multiply(b.p, a.p, d.p, MOD);d.p.resize(len);
    for (int i = 0;i < len;i++) b.p[i] = (b.p[i] - c.p[i] + d.p[i] + MOD) % MOD;
    b.p.resize(len);
    return b;
}
//保证[x ^ 0]f(x) = 1
Poly Sqrt(Poly a) {//多项式开根
    if (a.p.size() == 1) return a.p[0] = 1, a;
    const int len = a.p.size();
    Poly ta = a;ta.p.resize((len + 1) >> 1);
    Poly b = Sqrt(ta);b.p.resize(len);
    Poly inv_b = Inv(b);inv_b.p.resize(len);
    int lim = init_NTT(len << 1);
    NTT(a.p, lim, 1);NTT(b.p, lim, 1);NTT(inv_b.p, lim, 1);
    for (int i = 0;i < lim;i++) b.p[i] = 1ll * (b.p[i] * b.p[i] % MOD + a.p[i]) % MOD * _inv[2] % MOD * inv_b.p[i] % MOD;
    NTT(b.p, lim, -1);b.p.resize(len);
    return b;
}
//保证[x ^ 0]f(x) = 1
//k很大时,可以计算前对k模p (注意数论中是费马小定理,模p-1)
Poly Qpow(Poly a, int k) {//多项式快速幂
    int len = a.p.size();
    a = Ln(a);
    for (int i = 0;i < len;i++) a.p[i] = 1ll * a.p[i] * k % MOD;
    a = Exp(a);
    return a;
}
//k很大时,可以计算前对k模p和p-1记录在k1和k2(注意数论中是费马小定理,模p-1)
Poly Qpow_pro(Poly a, int k) {//任意首项多项式快速幂
    int k1 = k % MOD, k2 = k % (MOD - 1);
    int len = a.p.size();
    int shift = 0;
    for (int i = 0;i < len && a.p[i] == 0;i++) shift++;
    if (1ll * shift * k1 >= len) {
        for (int i = 0;i < len;i++) a.p[i] = 0;
        return a;
    }
    int inv_first = qp(a.p[shift], MOD - 2);int t = qp(a.p[shift], k2);
    for (int i = 0;i < len;i++) {
        if (i + shift < len) a.p[i] = 1ll * a.p[i + shift] * inv_first % MOD;
        else a.p[i] = 0;
    }
    a = Ln(a);
    for (int i = 0;i < len;i++) a.p[i] = 1ll * a.p[i] * k1 % MOD;
    a = Exp(a);
    shift *= k1;
    for (int i = len - 1;i >= shift;i--) a.p[i] = 1ll * a.p[i - shift] * t % MOD;
    for (int i = 0;i < shift;i++) a.p[i] = 0;
    return a;
}
//i^2=-1(mod p),对-1用二次剩余算出i
//i = 86583718 (mod 998244353)
//保证[x ^ 0]f(x) = 0
Poly Sin(Poly a) {//多项式sin
    const int I = 86583718;
    int inv_2i = qp(2 * I, MOD - 2);
    for (int i = 0;i < a.p.size();i++) a.p[i] = 1ll * a.p[i] * I % MOD;
    Poly b = Exp(a), c = Inv(b);
    for (int i = 0;i < a.p.size();i++) b.p[i] = 1ll * (MOD + b.p[i] - c.p[i]) % MOD * inv_2i % MOD;
    return b;
}
//保证[x ^ 0]f(x) = 0
Poly Cos(Poly a) {//多项式cos
    const int I = 86583718;
    for (int i = 0;i < a.p.size();i++) a.p[i] = 1ll * a.p[i] * I % MOD;
    Poly b = Exp(a), c = Inv(b);
    for (int i = 0;i < a.p.size();i++) b.p[i] = 1ll * (b.p[i] + c.p[i]) % MOD * _inv[2] % MOD;
    return b;
}

//记得Poly_init, 如果仅是乘法则不需要
//Poly读入和初始化时,记得取模. f.p[i] = -1  ==> f.p[i] = MOD-1 
多项式板子(新版)
namespace Cipolla {
    int mul(int x, int y) { return 1ll * x * y % MOD; }
    uint qp(uint a, int b) { uint res = 1; for (; b; b >>= 1, a = mul(a, a))  if (b & 1)  res = mul(res, a); return res; }
    int sqr_i;
    struct spc_Cp {
        int x, y;
        spc_Cp() { ; }
        spc_Cp(int x, int y) : x(x), y(y) {}
        inline spc_Cp operator * (const spc_Cp& t) const { return (spc_Cp) { (mul(x, t.x) + mul(mul(y, t.y), sqr_i)) % MOD, (mul(x, t.y) + mul(y, t.x)) % MOD }; }
    };
    spc_Cp qp(spc_Cp a, int b) {
        spc_Cp res = spc_Cp(1, 0);
        while (b) {
            if (b & 1) res = res * a;
            b >>= 1, a = a * a;
        }
        return res;
    }
    //解是res和MOD-res
    int Cipolla(int n) {
        srand(time(NULL));
        if (qp(n, MOD >> 1) == MOD - 1) return -1;
        ll t = mul(rand(), rand());
        while (qp((mul(t, t) - n) % MOD + MOD, MOD >> 1) == 1) t = 1ll * rand() * rand() % MOD;//找到非二次剩余的数,期望循环次数为2
        sqr_i = ((mul(t, t) - n) % MOD + MOD) % MOD;
        int res = qp(spc_Cp(t, 1), MOD + 1 >> 1).x;
        //return res;//返回任何一个解
        return min(res, MOD - res);//返回较小解
    }
}


int rev[N];//NTT/FFT反转二进制
namespace MTT {//任意模数多项式乘法
    const double PI = acos((double)-1);
    struct Cp {
        double x, y;
        Cp() { ; }
        Cp(double _x, double _y) : x(_x), y(_y) { }
        inline Cp operator + (const Cp& t) const { return (Cp) { x + t.x, y + t.y }; }
        inline Cp operator - (const Cp& t) const { return (Cp) { x - t.x, y - t.y }; }
        inline Cp operator * (const Cp& t) const { return (Cp) { x* t.x - y * t.y, x* t.y + y * t.x }; }
    }A[N], B[N], C[N], w[N / 2];

#define E(x) ll(x+0.5)%P

    void FFT(int n, Cp* a, int f) {
        for (int i = 0;i <= n - 1;i++) if (rev[i] < i) swap(a[i], a[rev[i]]);
        w[0] = Cp(1, 0);
        for (int i = 1;i < n;i <<= 1) {
            Cp t = Cp(cos(PI / i), f * sin(PI / i));
            for (int j = i - 2;j >= 0;j -= 2) w[j + 1] = t * (w[j] = w[j >> 1]);
            for (int l = 0;l < n;l += 2 * i) {
                for (int j = l;j < l + i;j++) {
                    Cp t = a[j + i] * w[j - l];
                    a[j + i] = a[j] - t;
                    a[j] = a[j] + t;
                }
            }
        }
        if (f == -1) for (int i = 0;i <= n - 1;i++) a[i].x /= n, a[i].y /= n;
    }

    void Multiply(vector<uint> a, vector<uint> b, vector<uint>& res, int P) {
        // [0,n-1]*[0,m-1]->[0,n+m-2]
        int n = a.size(), m = b.size();res.resize(n + m - 1);
        int S = (1 << 15) - 1;

        int R = 1, cc = -1;
        while (R <= n + m - 1) R <<= 1, cc++;
        for (int i = 1;i <= R;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << cc);
        for (int i = 0;i <= n - 1;i++) A[i] = Cp((a[i] & S), (a[i] >> 15));
        for (int i = 0;i <= m - 1;i++) B[i] = Cp((b[i] & S), (b[i] >> 15));
        for (int i = n;i <= R - 1;i++) A[i] = Cp(0, 0);
        for (int i = m;i <= R - 1;i++) B[i] = Cp(0, 0);

        FFT(R, A, 1), FFT(R, B, 1);
        for (int i = 0;i <= R - 1;i++) {
            int j = (R - i) % R;
            C[i] = Cp((A[i].x + A[j].x) / 2, (A[i].y - A[j].y) / 2) * B[i];
            B[i] = Cp((A[i].y + A[j].y) / 2, (A[j].x - A[i].x) / 2) * B[i];
        }
        FFT(R, C, -1), FFT(R, B, -1);
        for (int i = 0;i <= n + m - 2;i++) {
            ll a = E(C[i].x), b = E(C[i].y), c = E(B[i].x), d = E(B[i].y);
            res[i] = (a + ((b + c) << 15) + (d << 30)) % P;
        }
    }
    // void Multiply_db(vector<double> a, vector<double> b, vector<double>& res) {//答案double类型
    //     // [0,n-1]*[0,m-1]->[0,n+m-2]
    //     int n = a.size(), m = b.size();res.resize(n + m - 1);

    //     int R = 1, cc = -1;
    //     while (R <= n + m - 1) R <<= 1, cc++;
    //     for (int i = 1;i <= R;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << cc);
    //     for (int i = 0;i <= n - 1;i++) A[i] = Cp(a[i], 0);
    //     for (int i = 0;i <= m - 1;i++) B[i] = Cp(b[i], 0);
    //     for (int i = n;i <= R - 1;i++) A[i] = Cp(0, 0);
    //     for (int i = m;i <= R - 1;i++) B[i] = Cp(0, 0);

    //     FFT(R, A, 1), FFT(R, B, 1);
    //     for (int i = 0;i < R;i++) C[i] = A[i] * B[i];
    //     FFT(R, C, -1);
    //     for (int i = 0;i <= n + m - 2;i++) res[i] = C[i].x;
    // }

#undef E
}

int Add(int x, int y) { return (x + y >= MOD) ? x + y - MOD : x + y; }
int Dec(int x, int y) { return (x - y < 0) ? x - y + MOD : x - y; }
int mul(int x, int y) { return 1ll * x * y % MOD; }
uint qp(uint a, int b) { uint res = 1; for (; b; b >>= 1, a = mul(a, a))  if (b & 1)  res = mul(res, a); return res; }


namespace NTT {
    int sz;
    uint w[2500005], w_mf[2500005];
    int mf(int x) { return (1ll * x << 32) / MOD; }
    void init(int n) {
        for (sz = 2; sz < n; sz <<= 1);
        uint pr = qp(3, (MOD - 1) / sz);
        w[sz / 2] = 1; w_mf[sz / 2] = mf(1);
        for (int i = 1; i < sz / 2; i++)  w[sz / 2 + i] = mul(w[sz / 2 + i - 1], pr), w_mf[sz / 2 + i] = mf(w[sz / 2 + i]);
        for (int i = sz / 2 - 1; i; i--)  w[i] = w[i << 1], w_mf[i] = w_mf[i << 1];
    }
    void ntt(vector<uint>& A, int L) {
        for (int d = L >> 1; d; d >>= 1)
            for (int i = 0; i < L; i += (d << 1))
                for (int j = 0; j < d; j++) {
                    uint x = A[i + j] + A[i + d + j];
                    if (x >= 2 * MOD)  x -= 2 * MOD;
                    ll t = A[i + j] + 2 * MOD - A[i + d + j], q = t * w_mf[d + j] >> 32; int y = t * w[d + j] - q * MOD;
                    A[i + j] = x; A[i + d + j] = y;
                }
        for (int i = 0; i < L; i++)  if (A[i] >= MOD)  A[i] -= MOD;
    }
    void intt(vector<uint>& A, int L) {
        for (int d = 1; d < L; d <<= 1)
            for (int i = 0; i < L; i += (d << 1))
                for (int j = 0; j < d; j++) {
                    uint x = A[i + j]; if (x >= 2 * MOD)  x -= 2 * MOD;
                    ll t = A[i + d + j], q = t * w_mf[d + j] >> 32, y = t * w[d + j] - q * MOD;
                    A[i + j] = x + y; A[i + d + j] = x + 2 * MOD - y;
                }
        int k = (L & (-L));
        reverse(A.begin() + 1, A.end());
        for (int i = 0; i < L; i++) {
            ll m = -A[i] & (L - 1);
            A[i] = (A[i] + m * MOD) / k;
            if (A[i] >= MOD)  A[i] -= MOD;
        }
    }
}
int _inv[N];
void Poly_init(int mod = MOD) {
    _inv[1] = 1;
    for (int i = 2;i < N;i++) _inv[i] = 1llu * _inv[MOD % i] * (MOD - MOD / i) % MOD;
}
struct Poly {
    vector<uint> p;
    Poly(int n) { p.resize(n); }
    Poly(int n, int k) { p.resize(n);for (int i = 0;i < n;i++) p[i] = k; }
    Poly() {}
    uint& operator[](const int& k) { return p[k]; }
    Poly extend(int x) { Poly c = *this;c.p.resize(x);return c; }
    int deg() { return (int)p.size() - 1; }
    void resize(int n) { p.resize(n); }
    int size() { return p.size(); }
    void rev() { reverse(p.begin(), p.end()); }
};
Poly operator+ (Poly A, Poly B) {
    int n = A.size(), m = B.size();
    Poly c(max(n, m));
    for (int i = 0; i < n; i++)  c[i] = A[i];
    for (int i = 0; i < m; i++)  c[i] = Add(c[i], B[i]);
    return c;
}
Poly operator- (Poly A, Poly B) {
    int n = A.size(), m = B.size();
    Poly c(max(n, m));
    for (int i = 0; i < n; i++)  c[i] = A[i];
    for (int i = 0; i < m; i++)  c[i] = Dec(c[i], B[i]);
    return c;
}
Poly operator*(Poly A, Poly B) {//MOD=998244353,..a*2^k+1
    int n = A.deg() + B.deg() + 1;
    int lim;for (lim = 1; lim < n; lim <<= 1); NTT::init(lim);
    A.resize(lim); B.resize(lim);
    NTT::ntt(A.p, lim); NTT::ntt(B.p, lim);
    for (int i = 0; i < lim; i++)  A[i] = mul(A[i], B[i]);
    NTT::intt(A.p, lim); return A.extend(n);
}

// Poly operator*(Poly A, Poly B) {//任意模数多项式乘法
//     int n = A.deg() + B.deg() + 1;
//     Poly res(n);
//     MTT::Multiply(A.p, B.p, res.p, MOD);
//     return res.extend(n);
// }

// Poly operator*(Poly A, Poly B) {//答案double类型||如果答案不取模,改成longdouble即可
//     int n = A.deg() + B.deg() + 1;
//     Poly res(n);
//     MTT::Multiply_db(A.p, B.p, res.p);
//     return res.extend(n);
// }

Poly Dev(Poly A) {//多项式求导
    int n = A.size();
    for (int i = 1;i < n;i++) A[i - 1] = mul(A[i], i);
    return A[n - 1] = 0, A;
}
Poly Int(Poly A) {//多项式求积分
    int n = A.size();
    for (int i = n - 1;i >= 0;i--) A[i] = mul(A[i - 1], _inv[i]);//预处理逆元降低复杂度
    //for (int i = n - 1;i >= 0;i--) A[i] = mul(A[i - 1], qp(i, MOD - 2));//直接求逆元
    return A[0] = 0, A;
}
Poly Inv(Poly A) {//多项式乘法逆元
    int n = A.size();
    if (n == 1)  return A[0] = qp(A[0], MOD - 2), A;
    Poly B = A; B.resize((n + 1) >> 1); B = Inv(B);
    int lim; for (lim = 1; lim < (n << 1); lim <<= 1); NTT::init(lim);
    A.resize(lim); B.resize(lim);
    NTT::ntt(A.p, lim); NTT::ntt(B.p, lim);
    for (int i = 0; i < lim; i++)  A[i] = mul(Dec(2, mul(A[i], B[i])), B[i]);
    NTT::intt(A.p, lim); return A.extend(n);
}
Poly operator/(Poly A, Poly B) {
    A.rev(), B.rev();
    int n = A.size(), m = B.size();
    A.resize(n - m + 1), B.resize(n - m + 1);
    B = Inv(B);
    Poly C = A * B;C.resize(n - m + 1);C.rev();
    return C;
}
Poly operator%(Poly A, Poly B) {
    Poly C = A / B;
    return (A - (B * C).extend(A.size())).extend((int)B.size() - 1);
}

Poly __Inv(Poly A) {//任意模数多项式乘法逆元
    int n = A.size();
    if (n == 1) return A[0] = qp(A[0], MOD - 2), A;
    Poly B = A;B.resize((n + 1) >> 1); B = __Inv(B).extend(n);
    Poly C(1), D(1);
    MTT::Multiply(A.p, B.p, C.p, MOD);C.resize(n);
    MTT::Multiply(C.p, B.p, D.p, MOD);D.resize(n);
    for (int i = 0;i < n;i++) B[i] = Dec(Add(B[i], B[i]), D[i]);
    return B.extend(n);
}

//保证[x ^ 0]f(x) = 1
Poly Ln(Poly A) {//多项式对数
    Poly B; int n = A.size(); B.resize(n);
    for (int i = 1; i < n; i++)  B[i - 1] = mul(A[i], i); B[n - 1] = 0;
    B = (B * Inv(A)).extend(n);
    B = Int(B);
    return B;
}
//保证[x ^ 0]f(x) = 0
Poly Exp(Poly A) {//多项式指数
    int n = A.size();
    if (n == 1) return A[0] = 1, A;
    Poly B = A; B.resize((n + 1) >> 1); B = Exp(B).extend(n);
    Poly C = Ln(B);
    for (int i = 0; i < n; i++)  C[i] = Dec(A[i], C[i]); C[0] = Add(C[0], 1);
    return (B * C).extend(n);
}
Poly __Exp(Poly A) {//任意模数多项式指数
    int n = A.size();
    if (n == 1) return A[0] = 1, A;
    Poly B = A;B.resize((n + 1) >> 1); B = __Exp(B).extend(n);
    Poly C = Ln(B);
    Poly D(1), E(1);
    MTT::Multiply(B.p, C.p, D.p, MOD);D.resize(n);
    MTT::Multiply(B.p, A.p, E.p, MOD);E.resize(n);
    for (int i = 0;i < n;i++) B[i] = Add(Dec(B[i], D[i]), E[i]);
    return B.extend(n);
}

//保证[x ^ 0]f(x) = 1
Poly Sqrt(Poly A) {//多项式开根
    int n = A.size();
    if (n == 1) return A[0] = 1, A;
    Poly B = A;B.resize((n + 1) >> 1); B = Sqrt(B).extend(n);
    Poly C = Inv(B).extend(n);
    int lim; for (lim = 1; lim < (n << 1); lim <<= 1); NTT::init(lim);
    A.resize(lim); B.resize(lim);C.resize(lim);
    NTT::ntt(A.p, lim);NTT::ntt(B.p, lim);NTT::ntt(C.p, lim);
    for (int i = 0;i < lim;i++) B[i] = mul(mul(Add(mul(B[i], B[i]), A[i]), _inv[2]), C[i]);
    NTT::intt(B.p, lim);
    return B.extend(n);
}
Poly Sqrt_pro(Poly A) {//多项式开根
    int n = A.size();
    if (n == 1) return A[0] = Cipolla::Cipolla(A[0]), A;
    Poly B = A;B.resize((n + 1) >> 1); B = Sqrt_pro(B).extend(n);
    Poly C = (B * B).extend(n);
    for (int i = 0;i < n;i++) B[i] = mul(2, B[i]);
    for (int i = 0;i < n;i++) C[i] = Add(C[i], A[i]);
    C = C * Inv(B);
    return C.extend(n);
}

//保证[x ^ 0]f(x) = 1
//k很大时,可以计算前对k模p (注意数论中是费马小定理,模p-1)
Poly Qpow(Poly A, int k) {//多项式快速幂
    int n = A.size();Poly B = Ln(A);
    for (int i = 0;i < n;i++) B[i] = mul(B[i], k);
    return Exp(B);
}
//k很大时,可以计算前对k模p和p-1记录在k1和k2(注意数论中是费马小定理,模p-1)
Poly Qpow_pro(Poly a, int k) {//任意首项多项式快速幂
    int k1 = k % MOD, k2 = k % (MOD - 1);
    int n = a.size();
    int shift = 0;
    for (int i = 0;i < n && a[i] == 0;i++) shift++;
    if (1ll * shift * k1 >= n) {
        for (int i = 0;i < n;i++) a[i] = 0;
        return a;
    }
    int inv_first = qp(a[shift], MOD - 2);int t = qp(a[shift], k2);
    for (int i = 0;i < n;i++) {
        if (i + shift < n) a.p[i] = mul(a[i + shift], inv_first);
        else a[i] = 0;
    }
    a = Ln(a);
    for (int i = 0;i < n;i++) a[i] = mul(a[i], k1);
    a = Exp(a);
    shift *= k1;
    for (int i = n - 1;i >= shift;i--) a[i] = mul(a[i - shift], t);
    for (int i = 0;i < shift;i++) a[i] = 0;
    return a;
}

//i^2=-1(mod p),对-1用二次剩余算出i
//i = 86583718 (mod 998244353)
//保证[x ^ 0]f(x) = 0
const int I = 86583718;
Poly Sin(Poly A) {//多项式sin
    int n = A.size();
    int inv_2i = qp(mul(2, I), MOD - 2);
    for (int i = 0;i < n;i++) A[i] = mul(A[i], I);
    Poly B = Exp(A), C = Inv(B);
    for (int i = 0;i < n;i++) B[i] = mul(Dec(B[i], C[i]), inv_2i);
    return B;
}
//保证[x ^ 0]f(x) = 0
Poly Cos(Poly A) {//多项式cos
    int n = A.size();
    for (int i = 0;i < n;i++) A[i] = mul(A[i], I);
    Poly B = Exp(A), C = Inv(B);
    for (int i = 0;i < n;i++) B[i] = mul(Add(B[i], C[i]), _inv[2]);
    return B;
}
//保证[x ^ 0]f(x) = 0
Poly Arcsin(Poly A) {
    int n = A.size();
    Poly B = Dev(A);
    A = (A * A).extend(n);
    for (int i = 0;i < n;i++) A[i] = Dec(0, A[i]);A[0] = Add(1, A[0]);
    A = Sqrt(A);
    B = (B * Inv(A)).extend(n);
    B = Int(B);
    return B;
}
//保证[x ^ 0]f(x) = 0
Poly Arccos(Poly A) {
    int n = A.size();
    Poly B = Dev(A);for (int i = 0;i < n;i++) B[i] = Dec(0, B[i]);
    A = (A * A).extend(n);
    for (int i = 0;i < n;i++) A[i] = Dec(0, A[i]);A[0] = Add(1, A[0]);
    A = Sqrt(A);
    B = (B * Inv(A)).extend(n);
    B = Int(B);
    return B;
}
//保证[x ^ 0]f(x) = 0
Poly Arctan(Poly A) {
    int n = A.size();
    Poly B = Dev(A);
    A = (A * A).extend(n);
    A[0] = Add(1, A[0]);
    B = (B * Inv(A)).extend(n);
    B = Int(B);
    return B;
}

//记得Poly_init, 如果仅是乘法则不需要
//Poly读入和初始化时,记得取模. f.p[i] = -1  ==> f.p[i] = MOD-1 
//MTT的rev开lim大小,为方便一般3~4倍即可

 

基本概念

$\sum\limits_{i=1}$表示$\sum\limits_{i=1}^{\infty}$,$\prod\limits_{i=1}$表示$\prod\limits_{i=1}^{\infty}$

$a_i$与$a[i]$等价

$F^T(x)$表示$F(x)$的反转多项式,即对于多项式$F(x)=\sum\limits_{i=0}^{n}a_ix^i$,有$F^T[i]=F[n-i]$即$F^T(x)=\sum\limits_{i=0}^{n}a_{n-i}x^i$

规定$\mathcal A$表示组合类,其中每个组合对象$a\in \mathcal A$,都有$|a|$表示$a$的大小(可以理解为数字$a$),令计数序列$A[n]$表示$\mathcal A$中组合对象大小为$n$的元素的数量。可以理解为数字$n$的颜色数量/种类数量。

组合数: $C_{n}^{m}$ , $\binom{n}{m}$

斯特林子集数:  $SC_{n}^{m}$  ,  $n \brace m$ 。将$n$个不同元素,划分为$m$个非空子集的方案数。

斯特林转换数:  $SA_{n}^{m}$  ,  $n \brack m$ 。将$n$个不同元素,划分为$m$个非空圆排列的方案数。

下降幂:$x^{\underline{k}}=x(x-1)...(x-k+1)=\frac{x!}{(x-k)!}=C_{x}^{k}k!$

上升幂:$x^{\overline{k}}=x(x+1)...(x+k-1)=\frac{(x+k-1)!}{(x-1)!}=C_{x+k-1}^{k}k!$

多项式$F(x)$的第n项系数: $F[n]$ , $[x^n]F(x)$

多项式卷积: $H(x)=F(x)*G(x) = \sum\limits_{i=0}\sum\limits_{j=0}F[i]G[j]x^{i+j} $ ,则有$H[n]=\sum\limits_{i=0}^{n}F[i]G[n-i]$

狄利克雷卷积:$H=F*G, \;\; H(n)=\sum\limits_{d|n}F(d)G(\frac{n}{d})=\sum\limits_{d|n}F(\frac{n}{d})G(d)$。 本文多项式卷积和狄利克雷卷积均用$*$表示。

划分:将一个集合分成若干个不相交子集,并且这些子集的并为这个集合。如果两个划分的每个子集都相同,但是顺序不同,它们被认为是相同的划分。

艾佛森括号:$[x]=\begin{cases}1 , 表达式x为真 \\0 ,表达式x为假\end{cases} $

有/无标号:是否有序。无序可以理解为一个$std::set$,即放入集合的元素自动排好序。

有/无序:是否有序。

有/无标号有/无序集合:集合与集合之间是否有序,集合内部元素是否有序。

粗体字代表向量:如$\boldsymbol a$,$\boldsymbol b$,$\boldsymbol x$,$\boldsymbol y$​ 等。​

 

知识点索引

组合数学恒等式荟萃

递推式

$\binom{n}{m}=\binom{n-1}{m}+\binom{n-1}{m-1}$

分离式

$\binom{n}{x}\binom{x}{y}=\binom{n}{y}\binom{n-y}{x-y}$

吸收式

$\binom{n}{m}=\frac{n}{m}\binom{n-1}{m-1}$

斯特林子集数递推

$SC_{n}^{m}=SC_{n-1}^{m-1}+mSC_{n-1}^{m}$ 

斯特林转换数递推

$SA_{n}^{m}=SA_{n-1}^{m-1}+(n-1)SA_{n-1}^{m}$

平行求和

$\sum\limits_{i=0}^{n}\binom{r+i}{i}=\binom{r+n+1}{n}$

$\sum\limits_{i=0}^{n}\binom{r+i}{i}=\binom{r}{0}+\binom{r+1}{1}+\binom{r+2}{2}+...+\binom{r+n}{n}$

$=\binom{r+1}{0}+\binom{r+1}{1}+\binom{r+2}{2}+...+\binom{r+n}{n}$ ($\binom{r}{0}=\binom{r+1}{0}$)

$=\binom{r+2}{1}+\binom{r+2}{2}+...+\binom{r+n}{n}=\binom{r+n+1}{n}$ 

上指标求和

$\sum\limits_{i=0}^{n}\binom{i}{m}=\binom{n+1}{m+1}$   

$\sum\limits_{i=0}^{n}\binom{i}{m}=\binom{m}{m}+\binom{m+1}{m}+...+\binom{n}{m}$ (忽略为0的项) 

$=\binom{m+1}{m+1}+\binom{m+1}{m}+...+\binom{n}{m}$ ($\binom{m}{m}=\binom{m+1}{m+1}$)

$=\binom{m+2}{m+1}+\binom{m+2}{m}+...+\binom{n}{m}=\binom{n+1}{m+1}$ 

下指标求和

$\sum\limits_{i=0}^{m}C_{n}^{i}=C_{n}^{0}+C_{n}^{1}+...+C_{n}^{m}$

式子1.令$S_{n}^{m}=\sum\limits_{i=0}^{m}C_{n}^{i}$,那么显然有$S_{n}^{m+1}=S_{n}^{m}+C_{n}^{m+1}$

式子2.$S_{n+1}^{m}=C_{n+1}^{0}+...+C_{n+1}^m=C_{n+1}^{0}+(C_{n}^{0}+C_{n}^{1})+...+(C_n^{m-1}+C_n^{m})=2\cdot S_{n}^{m}-C_n^m$

如果$n,m$都是变量,用分块的思想。可以暴力预处理$f(x,y)$,其中$x=k\cdot \sqrt{n}$,$y=k\cdot \sqrt{m}$,询问的时候找接近的$f(x,y)$走,复杂度$n\sqrt{n}$ 。

范德蒙德卷积

$\sum\limits_{i=0}^{n}\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k}$

令$F(x)=(1+x)^{n+m}=(1+x)^n(1+x)^m$ ,按照多项式把两边的式子提取系数

$F[k]=\binom{n+m}{k}=\sum\limits_{i=0}^{n}\binom{n}{i}\binom{m}{k-i}$

推论:

$\sum\limits_{i=-r}^{s}\binom{n}{r+i}\binom{m}{s-i}=\binom{n+m}{s+r}$

令$i=i-r$ ,有$\sum\limits_{i=0}^{s+r}\binom{n}{i}\binom{m}{s-i+r}=\binom{n+m}{s+r}$

上升幂与下降幂的关系

$x^{\underline{n}}=(-1)^n(-x)^{\overline{n}}$

$x^{\overline{n}}=(-1)^n(-x)^{\underline{n}}$

证明:

$(-1)^n(-x)^{\overline{n}}=(-1)^n(-x)(-x+1)...(-x+n-1)=x(x-1)...(x-n+1)=x^{\underline{n}}$

$(-1)^n(-x)^{\underline{n}}=(-1)^n(-x)(-x-1)...(-x-n+1)=x(x+1)...(x+n-1)=x^{\overline{n}}$

普通幂变下降幂

$x^n=\sum\limits_{i=0}^{x} SC_{n}^{i}x^{\underline{i}}=\sum\limits_{i=0}^{x} SC_{n}^{i}C_{x}^{i}i! $

组合意义证明 :

等式左边:将$n$个不同的球放在$x$种不同的盒子,每个球有$x$种方法, 故共有 $x^n$种放法

等号右边:枚举非空盒子的个数$i$,从$x$个盒子中选出$i$个盒子,将$n$个球放在$i$个盒子里,我们知道将标号球放在无标号非空盒子中是斯特林子集数, 而现在盒子有标号,再对盒子进行全排列即可。

推论:通过对其进行二项式反演

$m^n=\sum\limits_{i=0}^{m}C_{m}^{i}SC_{n}^{i}i!$ (根据普通幂变下降幂)

$SC_{n}^{m}m!=\sum\limits_{i=0}^{m}(-1)^{m-i}C_{m}^{i}i^n$  (由二项式反演$f(m)=\sum\limits_{i=0}^{m}C_{m}^{i}g(i)\;⟺\;g(m)=\sum\limits_{i=0}^{m}(-1)^{m-i}C_{m}^{i}f(i) $)

$SC_{n}^{m}=\frac{1}{m!}\sum\limits_{i=0}^{m}(-1)^{m-i}C_{m}^{i}i^n$

下降幂变普通幂

$x^{\underline{n}}=\sum\limits_{i=0}^{n} SA_{n}^{i}(-1)^{n-i}x^i $

普通幂变上升幂

$x^n=\sum\limits_{i=0}^{n} SC_{n}^{i}(-1)^{n-i}x^{\overline{i}}=\sum\limits_{i=0}^{n} SC_{n}^{i}(-1)^{n-i}C_{x+i-1}^{i}i!$

上升幂变普通幂

$x^{\overline{n}}=\sum\limits_{i=0}^{n}SA_{n}^{i}x^i$

可以发现上升幂的幂级数各项系数是一行斯特林转换数。

斯特林转换数的和

$\sum\limits_{i=1}^{n}SA_{n}^{i}=n!$

可以理解为,从$1$到$n$每次放入元素,可以放在已有的任意元素的左边,代表和该元素在同一个环内,并且当前元素在环中的顺序是在该元素的左边。如果放在最右边则代表当前元素单独成一个环。可以发现这样不重不漏,可以拼出全排列。

将相乘化为相加

方便进行卷积

$ij=\binom{i+j}{2}-\binom{i}{2}-\binom{j}{2}$

生成函数集锦

OGF(普通生成函数)

解决组合类计数问题

元素无标号,更准确的说是计算组合时,常用OGF。$\{AAAA\}\times\{BBB\} → \{AAAABBB\}$

例子:使用$1,2,5$元支付$n$元的方案数,支付的顺序无影响。

EGF(指数生成函数)

解决排列类计数问题

元素有标号,更准确的说是计算排列时,常用EGF。$\{AAAA\}\times\{BBB\} → \{ABAABBA\},\{BBABAAA\},\{AABBABA\}...$用隔板法不难得到一共有$C_{7}^{4}$种

在普通生成函数的基础上, 除以排列数是为了先消去各自的有序性, 故在计算答案时即合并之后的整体, 需要再乘上整体的排列数, 加上有序性。
计算时看作是OGF计算,所以算完后要给对应的$x^n$乘上对应的阶乘n!

例子:使用红蓝绿三种颜色,对一行$n$个格子进行染色。与顺序是有关的,每种颜色选的个数相同,方案不一定等同。

PGF(概率生成函数)

用于概率期望领域

设$P(x)$为表达式“$x$”为真的概率

对于一个离散随机变量$X$,约定$X\in N$

其概率生成函数$F(x)=\sum\limits_{i=0}P(X=i)x^i$

显然有$\sum\limits_{i=0}P(X=i)=1$,故$F(1)=1$

$E(X)=\sum\limits_{i=0}P(X=i)i=F'(1)$

$E(X^{\underline{k}})=\sum\limits_{i=0}P(X=i)i=F^{(k)}(1)$

方差$Var(X)=E(\left(X-E(X)\right)^2)=E(X^2-2X\cdot E(X)+E(X)^2)=E(X^2)-E(X)^2$

尝试使用概率生成函数描述,我们凑出$E(X^2)$

注意到$X^{\underline{2}}+X^{\underline{1}}=X^2$,而$E(X^{\underline 2})=F''(1)$

$F''(1)+F'(1)=\sum\limits_{i=0}i(i-1)P(X=i)+\sum\limits_{i=0}iP(X=i)=\sum\limits_{i=0}i^2P(X=i)=E(X^2)$

$Var(X)=F'(1)^2-F''(1)-F'(1)$

二元生成函数

$F(x,y)=\sum\limits_{i=0}\sum\limits_{j=0}C_{i}^{j}x^iy^j$

 

如何辨别使用EGF/OGF?

T15,集合内元素之间无序,集合之间有序,集合之间相互独立,不同集合元素之间有序。EGF

T17,集合内元素之间有序,集合之间无序,集合之间不是相互独立,不同集合元素之间有序。EGF

T18,集合内元素之间无序,集合之间有序,集合之间不是相互独立,不同集合元素之间有序。EGF

T21,集合内元素之间有序,集合之间无序,集合之间不是相互独立,不同集合元素之间有序。EGF

T22,集合内元素之间无序,集合之间有序,集合之间相互独立,不同集合元素之间有序。EGF

T23,集合内元素之间有序,集合之间无序,集合之间相互独立,但是不同集合元素之间无序(因为两个集合之间的元素不能调换)。所以这里是OGF

至此我们明白,如果不同集合之间的元素可以进行调换,并且调换后成为另一个不同的集合,那么就使用EGF。

观察上面的例子,还能发现当集合无序且不是相互独立时,需要对集合卷积的结果再除以集合个数的阶乘

(集合之间相互独立,即指两个不同集合的元素不能调换。)

 

常用幂级数(生成函数封闭形式)

1.

$f(x)=1+x+x^2+...=\frac{1}{1-x}$

推论:$f(ax)=\sum\limits_{i=0}(ax)^{i}=\frac{1}{1-ax}$,$f(x^k)=\sum\limits_{i=0}x^{ki}=\frac{1}{1-x^k}$,$f((ax)^k)=\sum\limits_{i=0}(ax)^{ki}=\frac{1}{1-(ax)^k}$

2.

$e^x=\frac{x^0}{0!}+\frac{x}{1!}+\frac{x^2}{2!}+\frac{x^3}{3!}+...=\sum\limits_{i=0}\frac{x^i}{i!}$

推论:$e^{-x}=\sum\limits_{i=0}(-1)^i\frac{x^i}{i!}$ ,$\frac{e^x+e^{-x}}{2}=1+\frac{x^2}{2!}+\frac{x^4}{4!}+...=\sum\limits_{i=0}\frac{x^{2i}}{(2i)!}$,$\frac{e^x-e^{-x}}{2}=\frac{x^1}{1!}+\frac{x^3}{3!}+\frac{x^5}{5!}...=\sum\limits_{i=0}\frac{x^{2i+1}}{(2i+1)!}$

3.

$ln(\frac{1}{1-x})=\frac{x}{1}+\frac{x^2}{2}+\frac{x^3}{3}+...$ ,证明:两边求导得$\frac{1}{1-x}=1+x+x^2+...$

推论:$ln(1+x)=\frac{x}{1}-\frac{x^2}{2}+\frac{x^3}{3}+...$

4.

$x-\frac{x^3}{3!}+\frac{x^5}{5!}+...=sinx$

5.

$1-\frac{x^2}{2!}+\frac{x^4}{4!}+...=cosx$

6.

$x+\frac{1}{2}\frac{x^3}{3}+\frac{1\times 3}{2\times 4}\frac{x^5}{5}+\frac{1\times 3\times 5 }{2\times 4 \times 6}\frac{x^7}{7}=arcsinx$

7.

$1+\frac{a}{1!}x+\frac{a(a-1)}{2!}x^2+\frac{a(a-1)(a-2)}{3!}x^3+...=C_{a}{0}x^0+C_{a}^{1}x+C_{a}^{2}x^2+C_{a}^{3}x^3+...=(1+x)^a$ ,用广义二项式定理即可证明

8.

$f(x)=1+x+x^2+...+x^n=\frac{1-x^{n+1}}{1-x}$

证明:

$f(x)=1+x+x^2+...+x^n$ ,$xf(x)=x+x^2+...+x^{n}+x^{n+1}$ 

$f(x)-xf(x)=1-x^{n+1}$,即$f(x)=\frac{1-x^{n+1}}{1-x}$

$Fibonacci$数列的生成函数

$F(x)=\sum\limits_{i=1}fib[i]\cdot x^i=\frac{x}{1-x-x^2}$

证明:$F(x)=\sum\limits_{i=0}fib[i]x^i=fib[0]+fib[1]x+fib[2]x^2+...$

$xF(x)=\sum\limits_{i=0}fib[i]x^{i+1}=0x^0+fib[0]x+fib[1]x^2+fib[2]x^3...$

$x^2F(x)=\sum\limits_{i=0}fib[i]x^{i+2}=0x^0+0x^1+fib[0]x^2+fib[1]x^3+fib[2]x^4...$

于是有$(x+x^2)F(x)=0x^0+0x^1+fib[2]x^2+fib[3]x^3+...$ , $(x+x^2)F(x)+fib[0]+fib[1]x=F(x)$

即$(x+x^2)F(x)+x=F(x)$ ,则有$F(x)=\frac{x}{1-x-x^2}$

广义二项式定理

广义二项式系数

$C_{\alpha}^{k}=\frac{\alpha^{\underline {k}}}{k!}$ ,当$k$不为整数时,下降幂仍有定义。也就是:

其中$C_{\alpha}^{k}=\frac{\alpha(\alpha-1)(\alpha-2)...(\alpha-k+1)}{k!}$

广义二项式定理

$(x+y)^{\alpha}=\sum\limits_{i=0}^{\infty}C_{\alpha}^{k}x^{\alpha-k}y^k$​

上指标反转

上指标,即$\left(\begin{array}\ n\\m \end{array}\right)$中的$n$,$C_{n}^{m}$中的$n$。

处理上指标为负数的组合数

$C_{r}^{k}=\frac{r(r-1)...(r-k+1)}{k!}=\frac{(-r+k+1)...(-r)}{k!}=(-1)^kC_{k-r-1}^{k}$

对下降幂很熟的话也可以直接写出来:$r^{\underline{k}}=(-1)^k(-r)^{\overline{k}}=(-1)^k(k-r-1)^{\underline{k}}$ 。

处理$C_{2n}^{n}$型组合数

考虑下降幂的加倍公式:$x^{\underline{k}}(x-\frac{1}{2})^{\underline{k}}=x(x-\frac{1}{2})(x-1)(x-\frac{3}{2})...(x-k+1)(x-k+\frac{1}{2})$

$=2^{-2k}2x(2x-1)(2x-2)...(2x-2k+1)=\frac{(2x)^{\underline{2k}}}{2^{2k}}$

令$x=k=n$,能得到$n^{\underline{n}}(n-\frac{1}{2})^{\underline{n}}=\frac{(2n)^{\underline{2n}}}{2^{2n}}=\frac{(2n)!}{2^{2n}}$

两边同时除以$(n!)^2$:$C_{n}^{n}C_{n-\frac{1}{2}}^{n}=\frac{2n^{\underline{n}}}{n!}{2^{2n}}$

即$C_{n-\frac{1}{2}}^{n}=C_{2n}^{n}2^{2n}$

使用上指标反转:$(-1)^{n}C_{-\frac{1}{2}}^{n}=C_{2n}^{n}2^{2n}$ 

$=(-4)^{n}C_{-\frac{1}{2}}^{n}=C_{2n}^{n}$

$\sum\limits_{i=0}C_{2i}^{i}x^i=\sum\limits_{i=0}C_{-\frac{1}{2}}^{i}(-4x)^i$

逆用广义二项式定理,得到

$\sum\limits_{i=0}C_{2i}^{i}x^i=(1-4x)^{-\frac{1}{2}}$

 

推论1:

$(1+x)^n=\sum\limits_{i=0}^{\infty}C_{n}^{i}x^i$

推论2:

${(1-x)^n}=\sum\limits_{i=0}^{\infty}C_{n}^{i}(-x)^i=\sum\limits_{i=0}^{\infty}(-1)^iC_{n}^{i}x^i$

推论3:

$\frac{1}{(1-x)^n}=(1-x)^{-n}=(\frac{1}{1-x})^n=(1+x+x^2+...)^n$

$=\sum\limits_{i=0}^{\infty}C_{-n}^{i}(-x)^i=\sum\limits_{i=0}^{\infty}C_{n+i-1}^{n-1}x^i$

可以直接由隔板法推得:即$n$个$(1+x+x^2+...)$相乘,$x^i$的系数为$n$个方程中每一项选一个并乘起来。

即$x_1+x_2+...+x_n=i$的非负整数解的组数

或用上指标反转$(-1)^iC_{-n}^{i}=C_{i+n-1}^{i}$,也可证明。

生成函数与杨辉三角

将杨辉三角写成二元生成函数

$F[n][m]=C_{n}^{m}$ ,那么$F(x,y)=\sum\limits_{i=0}\sum\limits_{j=0}C_{i}^{j}x^iy^j=\sum\limits_{i=0}x^i(1+y)^i=\frac{1}{1-x-xy}$

如果将$y$替换成关于$x$的式子,或者将$x$替换成关于$y$的式子,从二元变成一元,$x^ay^b$的系数将会根据某些条件合并。

 

例如,令$y=x^k$,$[x^n]F(x,x^k)=[x^n]\sum\limits_{i=0}\sum\limits_{j=0}C_{i}^{j}x^ix^{kj}=\sum\limits_{i+jk=n}C_{i}^{j}$

令$x=y^k$,则$[y^n]F(y^k,y)=[y^n]\sum\limits_{i=0}\sum\limits_{j=0}C_{i}^{j}y^{ik}y^{j}=\sum\limits_{ik+j=n}C_{i}^{j}$

常常进行逆用

$\sum\limits_{i=0}^{m}C_{m}^{i}$ ,即为$\sum\limits_{i+j\times 0=m}C_{i}^{j}$,即令$y=x^0$得到$F(x,1)=\frac{1}{1-2x}$ ,$[x^m]F(x,1)=2^m$

 

$\sum\limits_{i=0}^{m}C_{i+m}^{2i}$ ,注意到$i+m-\frac{2i}{2}=m$ ,故$\sum\limits_{i-\frac{j}{2}=m}C_{i}^{j}$

令$y=x^{-\frac{1}{2}}$ ,$F(x,x^{-\frac{1}{2}})=\frac{1}{1-\sqrt{x}-x}$ ,$\sum\limits_{i=0}^{m}C_{i+m}^{2i}=[x^m]\frac{1}{1-\sqrt{x}-x}=[x^{2m}]\frac{1}{1-x^2-x}=[x^{2m+1}]\frac{x}{1-x^2-x}=Fib[2m+1]$

贝尔数

将$n$个元素划分为任意个非空子集的方案数

可以发现这是一行斯特林子集数的和,即$\sum\limits_{i=1}^{n}SC_{n}^{i}$,但是这样算比较慢

考虑更快的做法

元素之间有标号,考虑单个集合的EGF,$F(x)=\sum\limits_{i=1}\frac{x^i}{i!}=e^x-1$

集合之间无序,那么就是无序组合背包,$G(x)=\sum\limits_{i=0}\frac{F(x)^i}{i!}=e^{F(x)}=e^{e^x-1}$

模板题:P5748 集合划分计数

查看代码
void Solve(int TIME) {

    Poly_init();
    int t;cin >> t;
    Poly f(1e5 + 1);for (int i = 1;i <= 1e5;i++) f.p[i] = infact[i];
    f = Exp(f);
    while (t--) {
        int n;cin >> n;
        cout << fact[n] * f.p[n] % MOD << endl;
    }

}

 

圆排列

从$n$个元素取出$m$个进行圆排列的方案数是$c_{n}^{m}(m-1)!=\frac{A_n^m}{m!}$。

把排列数写成$EGF$,即$F(x)=\sum\limits_{i=0}i!\frac{x^i}{i!}=\sum\limits_{i=0}x^i=\frac{1}{1-x}$

把圆排列数写成$EGF$,$G(x)=\sum\limits_{i=1}(i-1)!\frac{x^i}{i!}=\sum\limits_{i=1}\frac{x^i}{i}=-\ln(1-x)$

发现$G(x)=lnF(x)$即$F(x)=e^{G(x)}$,更直观的理解就是一个圆排列,就相当于一个集合,一个排列可以被分解为多个置换环,也就是相当于多个圆排列,多个无标号的集合。

 

错位排列

错排递推式子:$f_n=(n-1)(f_{n-2}+f_{n-1})$,$n\ge 3$ ,$f_1=0,f_2=1$

另一种方法:$f_n=nf_{n-1}+(-1)^{n-2}$,$n\ge 2$ ,$f_1=0$

 

一个排列是错排 $\Longleftrightarrow$ 置换环中不存在自环(长度为1的环)

于是去掉圆排列第一项即可,也就是$-\ln(1-x)-x$。

错排是一个排列,也对应了多个置换环,于是其生成函数$D(x)=e^{-\ln(1-x)-x}$

不动点

给定$n,k$。一个一元映射$f(i)$,定义域和值域都是$[1,n]$。带入自变量$i\in[1,n]$,然后得到因变量$f(i)$后,再将其当作自变量,对于任意一个$i\in [1,n]$其重复$k−1$轮与重复$k$轮得到的结果是一样的。求这样的$f$的数量。

考虑建立$i \rightarrow f(i)$的边,问题的约束条件变成了:$i$走$k-1$步和走了$k$步走到同一个点$t$,显然$f(t)=t$。

由于每个点的出度是$1$,于是我们约定$f(i)$是$i$的父亲。而$f(t)=t$,我们约定它是根节点。于是$f$变成了一个森林。

每个叶子节点向上爬到根节点,距离不超过$k-1$步。于是整棵树的深度不超过$k$。

那么问题转化成了求不同的森林使得,森林中每个有根树的深度都不超过$k$。

考虑每棵有根树,设$n$个节点构成的深度不超过$k$的有根树数量是$g_{n,k}$,生成函数$G_k(x)$。其中$G_1(x)=1$。

$G_k(x)=x\times e^{G_{k-1}(x)}$ ,因为儿子之前是无序的,所以是若干个无标号集合。最后把他们连接到一个根节点,增加了一个元素所以乘上$x$。

再考虑森林,答案就是$H_k(x)=e^{G_k(x)}$

 

斯特林数的四类$nlogn$求法

斯特林子集数$\cdot$ 行

模板题:P5395 第二类斯特林数·行

$m^n=\sum\limits_{i=0}^{m}C_{m}^{i}SC_{n}^{i}i!$ (根据普通幂变下降幂)

$SC_{n}^{m}m!=\sum\limits_{i=0}^{m}(-1)^{m-i}C_{m}^{i}i^n$  (由二项式反演$f(m)=\sum\limits_{i=0}^{m}C_{m}^{i}g(i)\;⟺\;g(m)=\sum\limits_{i=0}^{m}(-1)^{m-i}C_{m}^{i}f(i) $)

$SC_{n}^{m}=\sum\limits_{i=0}^{m}(-1)^{m-i}\frac{1}{(m-i)!i!}i^n$

令$f[i]=SC_{n}^{i}$,$g[i]=\frac{i^n}{i!}$ ,$h[m-i]=\frac{(-1)^{m-i}}{(m-i)!}$即$h[i]=\frac{(-1)^i}{i!}$

则有$f=g*h$ ,FFT/NTT卷积即可

 

也可以从容斥的角度理解这个式子:

$\left\{\begin{array}\ n\\m \end{array}\right\}$先表示$m$个盒子装$n$个小球,每个盒子可以空也可以不空,这样就是$m^n$,之后考虑钦定一个盒子为空,也就是 $\left(\begin{array}\ m\\1 \end{array}\right)\cdot (m-1)^n$,还要考虑钦定两个,三个$\cdots$为空。最后容斥一下得到$\sum\limits_{i=0}^{m}(-1)^i\left(\begin{array}\ m\\i \end{array}\right)\cdot (m-i)^n$。由于这里的盒子是无序,而斯特林子集数的盒子有序,最后还要1除以$m!$,即$\frac{1}{m!}\sum\limits_{i=0}^{m}(-1)^i\left(\begin{array}\ m\\i \end{array}\right)\cdot (m-i)^n$

 

查看代码
Poly Stiring_2_row(int n) {//SC(n,i)
    Poly A(n + 1);for (int i = 0, infact_i = 1;i <= n;i++, infact_i = mul(infact_i, _inv[i])) A[i] = mul(((i & 1) ? MOD - 1 : 1), infact_i);
    Poly B(n + 1);for (int i = 0, infact_i = 1;i <= n;i++, infact_i = mul(infact_i, _inv[i])) B[i] = mul(qp(i, n), infact_i);
    A = A * B;
    return A;
}

 

斯特林转换数$\cdot$ 列

模板题:第一类斯特林数·列

$\left[\begin{array}\ n\\1 \end{array}\right]=(n-1)!$,于是先对$i=1$的情况构造出指数生成函数$S(x)=\sum\limits_{i=0}^{n}(i-1)!\frac{x^i}{i!}$

由生成函数的$exp$的组合意义得到,$\left[\begin{array}\ n\\m \end{array}\right]= \frac{S(x)^m}{m!}=\frac{(\ln(\frac{1}{1-x}))^m}{m!}$

套路:1个的生成函数是f,则n个的生成函数是$f^n$。

查看代码
 Poly Stiring_1_col(int n, int m) {//SA(i,m)
    int infact_m = 1;for (int i = 1;i <= m;i++) infact_m = mul(infact_m, _inv[i]);
    Poly A(n + 1);for (int i = 0;i <= n;i++) A[i] = qp(i, MOD - 2);
    A = Qpow_pro(A, m);
    for (int i = 0, fact_i = 1;i <= n;i++, fact_i = mul(fact_i, i)) A[i] = mul(mul(A[i], infact_m), fact_i);
    return A;
}

 

斯特林反演

$f(n)=\sum\limits_{i=0}^{n}SC_{n}^{i}g(i)\;⟺\;g(n)=\sum\limits_{i=0}^{n}(-1)^{n-i}SA_{n}^{i}f(i)$

$f(n)=\sum\limits_{i=0}^{n}(-1)^{n-i}SC_{n}^{i}g(i)\;⟺\;g(n)=\sum\limits_{i=0}^{n}SA_{n}^{i}f(i)$

$f(n)=\sum\limits_{i=n}SC_{i}^{n}g(i)\;⟺\;g(n)=\sum\limits_{i=n}(-1)^{i-n}SA_{i}^{n}f(i)$

$f(n)=\sum\limits_{i=n}(-1)^{i-n}SC_{i}^{n}g(i)\;⟺\;g(n)=\sum\limits_{i=n}SA_{i}^{n}f(i)$

二项式反演

二项式反演的四种形式

$f(n)=\sum\limits_{i=0}^{n}(-1)^iC_{n}^{i}g(i)\;⟺\;g(n)=\sum\limits_{i=0}^{n}(-1)^iC_{n}^{i}f(i)$

$f(n)=\sum\limits_{i=0}^{n}C_{n}^{i}g(i)\;⟺\;g(n)=\sum\limits_{i=0}^{n}(-1)^{n-i}C_{n}^{i}f(i)$

$f(n)=\sum\limits_{i=n}(-1)^iC_{i}^{n}g(i)\;⟺\;g(n)=\sum\limits_{i=n}(-1)^{i}C_{i}^{n}f(i)$

$f(n)=\sum\limits_{i=n}C_{i}^{n}g(i)\;⟺\;g(n)=\sum\limits_{i=n}(-1)^{i-n}C_{i}^{n}f(i)$

用生成函数随便证明一下第二个(其他三个也是基本一样的):

$f[n]=\sum\limits_{i=0}^{n}C_{n}^{i}g[i]=\sum\limits_{i=0}^{n}\frac{n!}{(n-i)!i!}g[i]$

$\frac{f[n]}{n!}=\sum\limits_{i=0}^{n}\frac{1}{(n-i)!}\frac{g[i]}{i!}$

$F[n]=\sum\limits_{i=0}^{n}\frac{1}{(n-i)!}G[i]$  (令$G[i]=\frac{g[i]}{i}$,$F[i]=\frac{f[i]}{i}$)

发现这是一个卷积,并且由于$\sum\limits_{i=0}\frac{x^i}{i!}=e^x$

于是$F=e^x*G $ ,那么$G=e^{-x}*F$

那么$\frac{g[n]}{n!}=\sum\limits_{i=0}^{n}\frac{(-1)^{n-i}}{(n-i)!}\frac{f[i]}{i!}$

那么$g[n]=\sum\limits_{i=0}^{n}\frac{(-1)^{n-i}n!}{(n-i)!i!}f[i]=\sum\limits_{i=0}^{n}(-1)^{n-i}C_{n}^{i}f[i]$

 

 

共有$n$件物品,设$f(x)$表示钦定x个物品非法,其余物品任意的方案数 , $g(x)$表示刚好x个物品非法,其余物品合法的方案数

由于这个状态设计,$f(x)$往往是好算的。又因为$f(x)=\sum\limits_{i=x}^{n}C_{i}^{x}g(i)$ ,则通过反演有 $g(x)=\sum\limits_{i=x}^{n}(-1)^{i-x}C_{i}^{x}f(i)$

算出$f(x)$后即可通过该式得出$g(x)$

 

注:二项式反演中的钦定函数$f(x)$,不能理解单纯的后缀和。同一种方案可能包含多种钦定的情况,也就是说会重复统计。对于恰好选择$i$个的$g(i)$,在$f(n)$中被计算了$C_{i}^{n}$次。    

 

生成函数的k阶前缀和与差分

只需要卷上全1序列即$\frac{1}{1-x}$即可做一次前缀和。求$f(x)$的k阶前缀和,即$f(x)(1-x)^{-k}$

只需要卷上${1-x}$即可做一次差分。求$f(x)$的k阶差分,即$f(x)(1-x)^{k}$

P5488 差分与前缀和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

$(x+y)^{\alpha}=\sum\limits_{i=0}^{\infty}C_{\alpha}^{k}x^{\alpha-k}y^k$​

其中$C_{\alpha}^{k}=\frac{\alpha(\alpha-1)(\alpha-2)...(\alpha-k+1)}{k!}$

$(\frac{1}{1-x})^k=(1-x)^{-k}=\sum\limits_{i=0}^{\infty}\frac{-k(-k-1)...(-k-i+1)}{i!}\times (-x)^i$

$=\sum\limits_{i=0}^{\infty}\frac{-k(-k-1)...(-k-i+1)}{i!}\times x^i\times (-1)^i=\sum\limits_{i=0}^{\infty}\frac{k(k+1)...(k+i-1)}{i!}\times x^i\times (-1)^{2i}$

$=\sum\limits_{i=0}^{\infty}\frac{k(k+1)...(k+i-1)}{i!}\times x^i=\sum\limits_{i=0}^{\infty} C_{k+i-1}^{i}x^i$

可以递推预处理$C_{k-1}^{0}=1,C_{k+i-2}^{i-1}\cdot \frac{k+i-1}{i}=C_{k+i-1}^{i}$

$k$阶差分,可以得到$(1-x)^k=\sum\limits_{i=0}^{\infty}C_k^i(-1)^i x^i$

同样可以递推预处理,$C_{k}^i=C_{k}^{i-1}\times \frac{k-i+1}{i}$

查看代码
 void Solve() {
    string sk;int n, t;cin >> n >> sk >> t;
    ll k = 0;for (auto i : sk) k = (k * 10 + i - '0') % MOD;
    Poly A(n);for (int i = 0;i < n;i++) cin >> A[i];
    Poly B(n);
    if (t == 0) {
        B[0] = 1;
        for (int i = 1;i < n;i++) {
            B[i] = 1LL * B[i - 1] * (k + i - 1) % MOD * qp(i, MOD - 2) % MOD;
        }
    }
    else {
        B[0] = 1;
        for (int i = 1;i < n;i++) {
            B[i] = 1LL * (MOD - B[i - 1]) % MOD * (k - i + 1) % MOD * qp(i, MOD - 2) % MOD;
        }
    }
    A = A * B;
    for (int i = 0;i < n;i++) {
        cout << A[i] << " \n"[i == n - 1];
    }

}

本题也可以直接多项式快速幂,常数略大。

 

差卷积

给定$\sum\limits_{i=k}^{n}A[i]B[i-k]$

首先令$i=i+k$ ,那么$\sum\limits_{i=0}^{n-k}A[i+k]B[i]$

令$A^T$为反转后的$A$,即$A^T[x]=A[n-x]$ 。

于是就有$C[n-k]=\sum\limits_{i=0}^{n-k}A^T[n-i-k]B[i]$

令$t=n-k$ ,就有$C[t]=\sum\limits_{i=0}^{t}A^T[t-i]B[i]$

于是就有$C=A^T * B$

多项式平移

由$F(x)=\sum\limits_{i=0}^{n}F[i]x^i$得到$F(x+c)$。

$F(x+c)=\sum\limits_{i=0}^{n}F[i](x+c)^i=\sum\limits_{i=0}^{n}F[i]\sum\limits_{j=0}^{i}C_{i}^{j}x^jc^{i-j}$

$=\sum\limits_{i=0}^{n}F[i]\sum\limits_{j=0}^{i}\frac{i!}{j!(i-j)!}x^jc^{i-j}=\sum\limits_{i=0}^{n}i!F[i]\sum\limits_{j=0}^{i}\frac{1}{j!(i-j)!}x^jc^{i-j}$

$=\sum\limits_{j=0}^{n}\frac{x^j}{j!}\sum\limits_{i=j}^{n}i!F[i]\frac{1}{(i-j)!}c^{i-j}$ (交换枚举次序)

令$G(x)=F(x+c) $ ,$H[i]=i!F[i]$ ,$I[i-j]=\frac{c^{i-j}}{(i-j)!}$

$G(x)=\sum\limits_{j=0}^{n}\frac{x^j}{j!}\sum\limits_{i=j}^{n}H[i]I[i-j]$ , 即$G[j]=\frac{1}{j!}\sum\limits_{i=j}^{n}H[i]I[i-j]$

然后即可使用差卷积做。

不同根的有理展开定理

对于多项式$f(x)=\sum\limits_{i=0}^{n}a_ix^i$ ,反转多项式(又称反射多项式)$f^T(x)=\sum\limits_{i=0}^{n}a_{n-i}x^i$

设$z_1,z_2,...,z^n$为$f^T(x)$的$n$个根。

若有$g(x)=\frac{p(x)}{f(x)}$ ($p(x)$是一个小于$n$次的多项式)

则有 $g[i]=\sum\limits_{k=1}^{n}\frac{-z_kp(\frac{1}{z_k})}{f'(\frac{1}{z_k})}x^i$

 

求导法(常微分方程ODE)

对常微分方程提取系数可以帮助我们洞察递推式和半在线卷积,也可以进一步用于复合推导。

几个显然的事实

$[x^n]F'(x)=(n+1)[x^{n+1}]F(x)$

$[x^n](xF(x))=[x^{n-1}]F(x)$

$[x^n](xF'(x))=n[x^{n}]F(x)$

然后可以根据这个提取系数,进行推导。

经典的常微分方程

1

$F(x)=e^{A(x)} \;⇒\; F'(x)=A'(x)F(x) $

2

$F(x)=\sum\limits_{i=0}^{k}\frac{x^i}{i!} \;⇒\; F'(x)=\sum\limits_{i=0}^{k-1}\frac{x^i}{i!}=F(x)-\frac{x^k}{k!} $

3

$F(x)=\sum\limits_{i=0}i!x^i \;⇒\; F'(x)=\sum\limits_{i=1}i!\cdot i x^{i-1}=\sum\limits_{i=0}(i+1)!\cdot (i+1) x^{i}$

$=\sum\limits_{i=0}(i+2)! x^{i}-\sum\limits_{i=0}(i+1)! x^{i}$,即$x^2F'(x)=\sum\limits_{i=0}(i+2)!x^{i+2}-x\sum\limits_{i=0}(i+1)!x^{i+1}=\sum\limits_{i=2}i!x^{i}-x\sum\limits_{i=1}i!x^{i}$

$=\sum\limits_{i=0}i!x^{i}-1-x-x(\sum\limits_{i=0}i!x^{i}-1)=F(x)-1-xF(x)$,也就是$x^2F'(x)=(1-x)F(x)-1$

4

$F(x)=\sum\limits_{i=0}\frac{x^i}{i!(i+k)!} \;⇒\; F'(x)=\sum\limits_{i=1}\frac{x^{i-1}}{(i-1)!(i+k)!}=\sum\limits_{i=0}\frac{x^{i}}{i!(i+k+1)!} $ ,没写完待补充。。。

 

经典模型

数的拆分

分拆数(正整数可重复无序拆分)

模板题:LOJ6268. 分拆数

将正整数$n$拆成几个正整数的和。拆法是与顺序无关的。 比如$5=1+4$和$5=4+1$,是同一种拆分方案。

由于拆分是无序的,我们将数字从小到大排列,表示成$n=\sum\limits_{i=1}^{}cnt_{i}i$,其中$cnt_i$表示数字i出现的次数

$F(x)=(1+x+x^2+...)(1+x^2+(x^2)^2+...)(1+x^3+(x^3)^2+...)...=\frac{1}{1-x}\frac{1}{1-x^2}\frac{1}{1-x^3}..=\prod\limits_{i=1}\frac{1}{1-x^i}$

$\prod\limits_{i=1}\frac{1}{1-x^i}=e^{\sum_\limits{i=1}ln(\frac{1}{1-x^i})}$ (通过$ln$和$exp$)

$=e^{\sum_\limits{i=1}\sum_\limits{j=1}\frac{x^{ij}}{j}}$(由$ln(\frac{1}{1-x})=\frac{x}{1}+\frac{x^2}{2}+\frac{x^3}{3}+...$,有$ln(\frac{1}{1-x^i})=\frac{x^i}{1}+\frac{(x^i)^2}{2}+\frac{(x^i)^3}{3}+...$)

指数上这个求和式的复杂度是$\frac{n}{1}+\frac{n}{2}+...+\frac{n}{n}=n(\frac{1}{1}+\frac{1}{2}+...+\frac{1}{n})=nlogn$

多项式$exp$复杂度$nlogn$ 。总时间复杂度$nlogn$。

查看代码
 void Solve(int TIME) {

    Poly_init();
    int n;cin >> n;
    Poly f(n + 1);
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j * i <= n;j++) {
            f.p[i * j] += qp(j, MOD - 2);
        }
    }
    
    f = Exp(f);
    for (int i = 1;i <= n;i++) cout << f.p[i] << endl;

}

扩展:如果每个数字$i$有$A[i]$种颜色(保证$A[0]=0$),并且每个数字的每个颜色都可以无限次使用,并且按字典序排序后,对于两个划分方案只要任意一个位置的颜色或数字不同,就算作不同的方案。

$ans=\prod\limits_{i=1}(\frac{1}{1-x^i})^{A[i]}$

这其实是个$MSET$构造。如果用$\mathcal A$表示组合类,那么元素大小$|x|$定义为拆分出的数字x。$A[x]$表示数字为x的颜色数量。

 

正整数可重复有序拆分

由于拆分是有序的,考虑每个位置的数是什么。

即$(x+x^2+...)(x+x^2+...)..$ (不能是0,所以去掉了$x^0$这一项)

枚举拆分出的数字的数量$k$,设总方案数的生成函数$ans(x)$

$ans(x)=\sum\limits_{k=0}\underbrace{(x+x^2+...)(x+x^2+...)...}_{共k项}=\sum\limits_{k=0}\prod\limits_{i=1}^{k}(x+x^2+...)$

$=\sum\limits_{k=0}(x+x^2+...)^k=\sum\limits_{k=0}(x+x^2+...)^k=\sum\limits_{k=0}(\frac{x}{1-x})^k=\frac{1}{1-\frac{x}{1-x}}$

$=\frac{1-x}{1-2x}=\frac{1}{2}+\frac{1}{2}\cdot \frac{1}{1-2x}$ ,而$\frac{1}{1-2x}=1+2x+(2x)^2+...$ ,则$F[n]=2^n\frac{1}{2}=2^{n-1}$

还有一种证明方法是: 利用隔板法,枚举分成$k$个数,$ans_k=C_{n-1}^{k-1}$即是$x_1+x_2+..+x_k=n$的正整数解的组数

$ans=\sum\limits_{k=1}^{n}C_{n-1}^{k-1}=2^{n-1}$

 

扩展:如果每个数字$i$有$A[i]$种颜色(保证$A[0]=0$),并且每个数字的每个颜色都可以无限次使用,并且对于两个划分方案只要任意一个位置的颜色或数字不同,就算作不同的方案。

那么$A(x)=\sum\limits_{i=1}A[i]x^i+A[0]x^0=\sum\limits_{i=0}A[i]x^i$,

$ans(x)=\sum\limits_{k=0}A(x)^k=\frac{1}{1-A(x)}$   (类比$\sum\limits_{i=0}x^i=\frac{1}{1-x}$)

这其实是一个$SEQ$构造。如果用$\mathcal A$表示组合类,那么元素大小$|x|$定义为拆分出的数字x。$A[x]$表示数字为x的颜色数量。

不重复无序拆分

考虑每个数,只有$1$个或$0$个的情况。生成函数$F(x)=(1+x^1)(1+x^2)(1+x^3)...$ 

不重复有序拆分

只能想到指数级别的做法...状压然后枚举,如果和刚好为$n$,就将$1$的数量的阶乘计入答案

将$n$划分成多个不大于$m$的数,可重复,无序

考虑每个不大于m的数的数量

$F(x)=(1+x+x^2+...)(1+x^2+(x^2)^2+...)...(1+x^m+(x^m)^2+...)=\prod\limits_{i=1}^{m}\frac{1}{1-x^i}$

dp方法:

$f(n, m)$表示整数$n$的划分中, 每个数都不大于$m$的划分方案.
转移时, 可以分成两种情况
第一种情况 : 划分方案中每个数都小于$m$, 因此方案$f(n, m - 1)$
第二种情况 : 划分方案中至少有一个数为$m$, 那么就在$n$中减去$m$, 得到$f(n - m, m)$
故$f(n, m) = f(n, m - 1) + f(n - m, m)$
将$n$划分成多个不大于$m$的数,不可重复,无序

考虑每个数不大于$m$的数,只有$1$个或$0$个的情况。生成函数$F(x)=\prod\limits_{i=1}^{m}(1+x^i)$ 

dp方法:

$f(n, m) = f(n, m - 1) + f(n - m, m - 1)$
 

 

小球入盒

现在有$n$个球,求放入$m$个箱子的方案数(球必须全部放入箱子中)

模板题:P5824 十二重计数法

查看代码
 void Solve(int TIME) {

    Poly_init();
    int n, m;cin >> n >> m;
    Poly g(n + 1);for (int i = 0;i <= n;i++) g.p[i] = qp(i, n) * infact[i] % MOD;
    Poly h(n + 1);for (int i = 0;i <= n;i++) h.p[i] = ((i & 1) ? MOD - 1 : 1) * infact[i] % MOD;
    Poly f = g * h;

    Poly ff(n + 1);
    for (int i = 1;i <= m;i++) {
        for (int j = 1;j * i <= n;j++) {
            (ff.p[i * j] += inv(j)) %= MOD;
        }
    }
    ff = Exp(ff);

    cout << qp(m, n) << endl;
    cout << (n > m ? 0 : fact[m] * infact[m - n] % MOD) << endl;
    cout << (m > n ? 0 : f.p[m] * fact[m] % MOD) << endl;

    int res4 = 0;for (int i = 0;i <= min(m, n);i++) res4 = (res4 + f.p[i]) % MOD;cout << res4 << endl;
    cout << (n > m ? 0 : 1) << endl;
    cout << (m > n ? 0 : f.p[m]) << endl;

    cout << comb(n + m - 1, m - 1) << endl;
    cout << (n > m ? 0 : comb(m, n)) << endl;
    cout << comb(n - 1, m - 1) << endl;


    cout << ff.p[n] << endl;
    cout << (n > m ? 0 : 1) << endl;
    cout << (m > n ? 0 : ff.p[n - m]) << endl;

}

大致框架:

球相同时,可以看作是数的划分,然后看盒,如果盒相同便是无序划分,盒不同便是有序划分;

球不同时,如果盒相同,则是斯特林子集数,盒不同便是斯特林子集数算上盒子的顺序。当然斯特林子集数一个重要条件就是无空箱,这里需要额外再作讨论。

球同,箱不同,允许空箱

$x_1+x_2+...+x_m=n$的非负整数解的个数 。考虑先给所有盒子放上$1$个球。

然后隔板法。$C_{n+m-1}^{m-1}$

球同,箱不同,无空箱

即$x_1+x_2+...+x_m=n$的正整数解的个数 

隔板法。 $C_{n-1}^{m-1}$

球同,箱不同,箱内至多一个球

当$n>m$,方案数为0,因为每个球必须放入箱子中

当$n\leq m$,为$C_{m}^{n}$,即选择哪几个箱子放入球

球同,箱同,允许空箱

将n无序的拆成m个正整数的方案数。

由于盒子相同,考虑枚举$i$,拥有$i$个球的盒子的个数。显然$i_{max}=m$ 。

$F(x)=(1+x^0+(x^0)^2+...)(1+x+x^2+...)(1+x^2+(x^2)^2+...)..$ .

$=m\prod\limits_{i=1}^{m}(1+x^i+(x^i)^2+..)=\prod\limits_{i=1}^{m}\frac{m}{(1-x^i)}$ , $res=F[n]$ 。 由于$m$对于答案无影响,可以直接略去。

$F(x)=e^{ln\prod\limits_{i=1}^{m}\frac{1}{1-x^i}}=e^{\sum\limits_{i=1}^{m}ln\frac{1}{1-x^i}}=e^{\sum\limits_{i=1}^{m}\sum\limits_{j=1}\frac{x^{ij}}{j}}$

$res=[x^n]\prod\limits_{i=1}^{m}\frac{1}{(1-x^i)}=[x^n]e^{\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{\lfloor\frac{n}{i} \rfloor}\frac{x^{ij}}{j}}$

球同,箱同,无空箱

将$n$无序的拆成$m$个非负整数的方案数。

先给每个箱子放上一个球,再进行上述的操作。

$res=[x^{n-m}]\prod\limits_{i=1}^{m}\frac{1}{(1-x^i)}$

球同,箱同,箱内至多一个球

当$n>m$,方案数为$0$,因为每个球必须放入箱子中

当$n\leq m$,为$1$。 即$[n\leq m]$

球不同,箱不同,允许空箱

每个球有m种放法。$res=m^n$

球不同,箱不同,无空箱

多了有序性的斯特林子集数。$m!SC_{n}^{m}$

球不同,箱不同,箱内至多一个球

当$n>m$,方案数为$0$,因为每个球必须放入箱子中

当$n\leq m$,$m(m-1)...(m-n+1)=m^{\underline{n}}$

球不同,箱同,允许空箱

枚举$i$个盒子非空。$res=\sum\limits_{i=1}^{m}SC_{n}^{i}$

球不同,箱同,无空箱

斯特林子集数。$SC_{n}^{m}$

球不同,箱同,箱内至多一个球

当$n>m$,方案数为$0$,因为每个球必须放入箱子中

当$n\leq m$,为$1$。 即$[n\leq m]$

 

经典的组合构造

无标号(即$A(x)$无标号,$A(x)$之间相同)

$Sequence$构造(序列构造)

有序排列背包。

对于组合类$\mathcal A$,$SEQ(\mathcal A)$表示生成不定长序列的构造

$SEQ(\mathcal A)=\sum\limits_{k=0}\mathcal A^{k}$,规定$A[0]=0$

写成生成函数:$\mathcal B=SEQ(\mathcal A)\;⇒\;B(x)=\sum\limits_{k=0}A(x)^k=\frac{1}{1-A(x)}$

举例:$\mathcal C=\{0,1\}$,显然$|0|=|1|=1$(这里$|x|$表示组合对象也就是元素的大小)

记$\mathcal S$表示$01$串,即$\mathcal S=SEQ(\mathcal C)$

$C[x]=2x$ ,因为数量为$1$的元素有两个。 $S[x]=\frac{1}{1-C[x]}=\frac{1}{1-2x}$

 

$Amplification_k$构造($k$倍膨胀构造)

对于组合类$\mathcal A$,$AMP_k(\mathcal A)$表示$\mathcal A$中每个元素重复$k$次后的结果,也就是所有元素大小扩大$k$倍

$\mathcal B=AMP_k(\mathcal A) \;⇒\;B(x)=A(x^k)$

 

$Multiset$构造(多重集构造)

完全背包。

定义$G$为置换群列,$G$的元素中,($x_1$,$x_2$,$x_3$) ,($x_2$,$x_1$,$x_3$) ,($x_3$,$x_1$,$x_2$)等等都视作是相同的组合

$MSET(\mathcal A)=SEQ(\mathcal A)/G_{\mathcal A}$

可以发现$MSET$可以视作是不定长字典序不降序列的构造。

于是按照规定的顺序枚举$\mathcal A$的所有对象, 每次选择加入若干个(可以是$0$个)

$\mathcal B=MSET(\mathcal A)$

$B(x)=\prod\limits_{a\in \mathcal A}(1+x^{|a|}+(x^{|a|})^2+...)=\prod\limits_{a\in \mathcal A}\sum\limits_{k=0}x^{|a|k}=\prod\limits_{a\in \mathcal A}(\frac{1}{1-x^{|a|}})=\prod\limits_{i=1}(\frac{1}{1-x^i})^{A[i]}$

$=e^{ln{\prod\limits_{i=1}(\frac{1}{1-x^i})^{A[i]}}}=e^{{\sum\limits_{i=1}{A[i]}ln(\frac{1}{1-x^i})}}$

$=e^{{\sum\limits_{i=1}{A[i]}\sum\limits_{j=1}\frac{x^{ij}}{j}}}$ (到这一步,已经可以$O(nlogn)$暴力完成求和,再做多项式$exp$即可)

$=e^{{\sum\limits_{j=1}\frac{1}{j}\sum\limits_{i=1}{A[i]}{x^{ij}}}}$ (交换枚举次序)

$=e^{{\sum\limits_{j=1}\frac{1}{j}\sum\limits_{i=0}{A[i]}{(x^{j})^i}}}=e^{{\sum\limits_{j=1}\frac{A(x^j)}{j}}}=e^{{\sum\limits_{i=1}\frac{A(x^i)}{i}}}$

模板题:P4389 付公主的背包

查看代码
void Solve(int TIME) {

    Poly_init();
    int n, m;cin >> n >> m;
    vi cnt(m + 1);
    for (int i = 1;i <= n;i++) {
        int x;cin >> x;
        cnt[x]++;
    }
    Poly f(m + 1);
    for (int i = 1;i <= m;i++) {
        for (int j = 1;j * i <= m;j++) {
            (f.p[i * j] += cnt[i] * qp(j, MOD - 2) % MOD) %= MOD;
        }
    }
    f = Exp(f);
    for (int i = 1;i <= m;i++) cout << f.p[i] << endl;
    
}
$Powerset$构造

01背包。

枚举每个元素是否存在。 即$\mathcal B=PSET(\mathcal A) \;⇒\;B(x)=\prod\limits_{a\in \mathcal A}(1+x^{|a|}) =\prod\limits_{i=0 }(1+x^{i})^{A[i]}$

考虑用$ln$和$Exp$,$B(x)=e^{ln\prod\limits_{i=0}(1+x^i)^{A[i]}}=e^{\sum\limits_{i=0}{A[i]}ln(1+x^i)}=e^{\sum\limits_{i=0}{A[i]}\sum\limits_{j=1}\frac{(-1)^{j-1}x^{ij}}{j}}$

$=e^{\sum\limits_{j=1}\frac{(-1)^{j-1}}{j}\sum\limits_{i=0}{A[i]x^{ij}}}=e^{\sum\limits_{j=1}\frac{(-1)^{j-1}A(x^j)}{j}}$

指数上的部分,暴力$nlogn$即可。

 

有标号(即$A(x)$有标号,$A(x)$之间不同

有标号$Sequence$构造

同无标号$Sequence$构造。把$OGF$改成$EGF$即可。

$Set$构造($Exp$的组合意义)

无序组合背包。

$\mathcal B=SET(\mathcal A) \;⇒\;B(x)=\sum\limits_{i=0}\frac{A(x)^i}{i!}=e^{A(x)}$

把$A(x)$视作一个整体,可以发现$B(x)$是若干个$A(x)$生成的不定长无序集合(除以$i!$的意义就是将有序变成无序,注意这里由于每个都是$A(x)$,是同一种东西,所以要区分有序和无序。)。

举例:无向联通图就是$A(x)$,无向图就是$e^{A(x)}$ ,因为无向图是由若干个无向联通图无序组合而成。

可以这样理解,若干个无向连通图之间两两不同(表示$A(x)$之间有标号),但是有序排列起来还是同一张无向普通题,所以只需要无序组合起来。

 

 

题单

 

多项式与生成函数

1. CFgym103428M - 810975

知识点

二项式反演,多项式快速幂

题意

在$n-m$个$0$形成的$n-m+1$个空中,插入$m$个$1$,使得每个空中的$1$的个数的最大值刚好是$k$

题解

设$F(x)$为每个空中最多可以放$x$个$1$,那么$ans=F(k)-F(k-1)$ 

$F(k)=[x^m](1+x+x^{2}+x^{3}+\cdots+x^{k})^{n-m+1}$

$F(k-1)=[x^m](1+x+x^{2}+x^{3}+\cdots+x^{k-1})^{n-m+1}$

多项式快速幂即可,注意处理特殊情况

查看代码
 void Solve(int TIME) {
 
    Poly_init();
    int n, m, k;cin >> n >> m >> k;
    if (n < m) return cout << 0 << endl, void();
    if (n == 0) return cout << 1 << endl, void();
    if (m == 0) return cout << 1 << endl, void();
    if (k > m) return cout << 0 << endl, void();
    Poly p(m + 1);for (int i = 0;i <= k;i++) p.p[i] = 1;for (int i = k + 1;i <= m;i++) p.p[i] = 0;
    p = Qpow(p, n - m + 1);
    int res = p.p[m];
    if (k == 0) {
        cout << 0 << endl;return;
    }
    else if (k == 1) {
        cout << res << endl;return;
    }
    else {
        for (int i = 0;i < k;i++) p.p[i] = 1;for (int i = k;i <= m;i++) p.p[i] = 0;
        p = Qpow(p, n - m + 1);
        cout << ((res - p.p[m]) % MOD + MOD) % MOD << endl;
    }
}
 
 
二项式反演推式子也能做,并且快不少
$f(x)$的含义为钦定$x$个是不合法的方案数(即钦定$x$个空超过了$k$个/$k-1$个),$g(x)$的含义为刚好$x$个不合法(即刚好$x$个空超过了$k$个/$k-1$个)的方案数。
$ans=F(k)-F(k-1)$ 
$F(k)=g_{1}(0)=\sum\limits_{i=0}^{n-m+1}(-1)^{i}f_1(i) ,$     $f_{1}(x)=C_{n-m+1}^{x}C_{n-(k+1)x}^{n-m} ,$      $F(k)=\sum\limits_{i=0}^{n-m+1}(-1)^{i}C_{n-m+1}^{i}C_{n-(k+1)i}^{n-m}$
$F(k-1)=g_{2}(0)=\sum\limits_{i=0}^{n-m+1}(-1)^{i}f_2(i) ,$     $ f_{2}(x)=C_{n-m+1}^{x}C_{n-kx}^{n-m} , $     $F(k-1)=\sum\limits_{i=0}^{n-m+1}(-1)^{i}C_{n-m+1}^{i}C_{n-ki}^{n-m} $
$ans=\sum\limits_{i=0}^{n-m+1}(-1)^{i}C_{n-m+1}^{i}C_{n-(k+1)i}^{n-m} - \sum\limits_{i=0}^{n-m+1}(-1)^{i}C_{n-m+1}^{i}C_{n-ki}^{n-m} $
解释一下这里的$f_{1}(x)$,即 $x_1+x_2+...+x_{n-m+1}=m$这个方程组非负解的个数,由于钦定了$x$个不合法,就先给这$x$个分配$k+1$个,在剩下的$m-(k+1)x$中分配,也就是$x_1+x_2+...+x_{n-m+1}=m-(k+1)x$的非负解的个数,用隔板法易得为$C_{n-(k+1)x}^{n-m} $ ,又要在$n-m+1$个空中选$x$个为不合法的,故$f_{1}(x)=C_{n-m+1}^{x}C_{n-(k+1)x}^{n-m} $ 
 
查看代码
 void Solve(int TIME) {
 
    int n, m, k;cin >> n >> m >> k;
    int res = 0;
    for (int i = 0;i <= n - m + 1 && n - (k + 1) * i >= n - m;i++) {
        if (i & 1) {
            res = sub(res, mul(comb(n - m + 1, i), comb(n - (k + 1) * i, n - m)));
        }
        else {
            res = add(res, mul(comb(n - m + 1, i), comb(n - (k + 1) * i, n - m)));
        }
    }
    int res2 = 0;
    for (int i = 0;i <= n - m + 1 && n - (k)*i >= n - m;i++) {
        if (i & 1) {
            res2 = sub(res2, mul(comb(n - m + 1, i), comb(n - (k)*i, n - m)));
        }
        else {
            res2 = add(res2, mul(comb(n - m + 1, i), comb(n - (k)*i, n - m)));
        }
    }
 
    cout << sub(res, res2) << endl;
}
 

2. U206935 - 七选五 - 非酋的胜利

知识点

二项式反演,FFT/NTT

题解

令$f(x)$表示钦定$x$个填对的方案数,$f(x)=C_{m}^{x}C_{n-x}^{m-x}(m-x)!$

令$g(x)$表示刚好$x$个填对的方案数,$g(x)=\sum\limits_{i=x}^{m}(-1)^{i-x}C_{i}^{x}f(i)=\sum\limits_{i=x}^{m}(-1)^{i-x}C_{i}^{x}C_{m}^{i}C_{n-i}^{m-i}(m-i)!$ 

则$g(0)=\sum\limits_{i=0}^{m}(-1)^{i}C_{m}^{i}C_{n-i}^{m-i}(m-i)!$

求概率,于是再除以总方案数即可。$res=\frac{\sum\limits_{i=0}^{m}(-1)^{i}C_{m}^{i}C_{n-i}^{m-i}(m-i)!}{m!C_{n}^{m}}$

$=\frac{1}{m!C_{n}^{m}}\sum\limits_{i=0}^{m}(-1)^{i}\frac{m!}{i!(m-i)!}\frac{(n-i)!}{(n-m)!(m-i)!}(m-i)!=\frac{1}{C_{n}^{m}(n-m)!}\sum\limits_{i=0}^{m}(-1)^{i}\frac{(n-i)!}{i!}\frac{1}{(m-i)!}$

令$F_i=(-1)^{i}\frac{(n-i)!}{i!}$ ,$G_{m-i}=\frac{1}{(m-i)!}$即$G_i=\frac{1}{i!}$ ,卷积即可。复杂度$O(nlogn)$

查看代码
void Solve(int TIME) {

    int n;cin >> n;
    Poly f(n + 1), g(n + 1);
    for (int i = 0;i <= n;i++) f[i] = mul(mul(((i & 1) ? MOD - 1 : 1), fact[n - i]), infact[i]);
    for (int i = 0;i <= n;i++) g[i] = infact[i];
    f = f * g;
    for (int m = 1;m <= n;m++) {
        int res = f[m];
        cout << mul(mul(res, invcomb(n, m)), infact[n - m]) << endl;
    }

}

还有线性的做法,不知道怎么写的,先观望一下

 

3. P4931 情侣?给我烧了

知识点
 
二项式反演,FFT/NTT,常微分方程,$C_{2i}^{i}$的生成函数
 
题解

设$f(x)$表示钦定$x$对情侣和睦,$f(x)=C_{n}^{x}C_{n}^{x}x!2^x(2n-2x)!$

$g(x)$表示刚好$x$对情侣和睦,$g(x)=\sum\limits_{i=x}^{n}(-1)^{i-x}C_{i}^{x}f(i)=\sum\limits_{i=x}^{n}(-1)^{i-x}C_{i}^{x}C_{n}^{i}C_{n}^{i}i!2^i(2n-2i)!$

$g(x)=\sum\limits_{i=0}^{n-x}(-1)^{i}C_{i+x}^{x}C_{n}^{i+x}C_{n}^{i+x}(i+x)!2^{i+x}(2n-2i-2x)!$

$=\sum\limits_{i=0}^{n-x}(-1)^{i}\frac{(i+x)!}{i!x!}(\frac{n!}{(n-i-x)!(i+x)!})^2(i+x)!2^{i+x}(2n-2i-2x)!$

$=\frac{(n!)^22^x}{x!}\sum\limits_{i=0}^{n-x}(-1)^{i}\frac{2^{i}}{i!}(\frac{1}{(n-i-x)!})^2(2n-2i-2x)!$

令$F(i)=(-1)^i\frac{2^i}{i!}$ ,$G(n-i-x)=\frac{(2n-2i-2x)!}{((n-i-x)!)^2}$ 即 $G(i)=\frac{(2i)!}{((i)!)^2}$

卷积即可,可以过简单版

查看代码
 void Solve(int TIME) {

    Poly f(1001);Poly g(1001);
    vi pw2(1001);pw2[0] = 1;for (int i = 1;i <= 1000;i++) pw2[i] = pw2[i - 1] * 2 % MOD;
    for (int i = 0;i <= 1000;i++) f.p[i] = ((i & 1) ? MOD - 1 : 1) * pw2[i] % MOD * infact[i] % MOD;
    for (int i = 0;i <= 1000;i++) g.p[i] = fact[2 * i] * infact[i] % MOD * infact[i] % MOD;
    f = f * g;
    int t;cin >> t;
    while (t--) {
        int n;cin >> n;
        for (int i = 0;i <= n;i++) {
            cout << fact[n] * fact[n] % MOD * infact[i] % MOD * pw2[i] % MOD * f.p[n - i] % MOD << endl;
        }
    }
}
 
考虑加速这个过程。

发现前面是$F(x)=\sum\limits_{i=0}\frac{(-2)^i}{i!}x^i=\sum\limits_{i=0}\frac{(-2x)^i}{i!}=e^{-2x}$,后面是$G(x)=\sum\limits_{i=0}C_{2i}^{i}x^i=\frac{1}{\sqrt{1-4x}}$

令$H=F*G=\frac{e^{-2x}}{\sqrt{1-4x}}$   

然后求一下导,有 $H'(x)=\frac{-2e^{-2x}\cdot\sqrt{1-4x}-\frac{-4}{2\sqrt{1-4x}}\cdot e^{-2x}}{1-4x}=\frac{e^{-2x}(-2\sqrt{1-4x}+\frac{2}{\sqrt{1-4x}})}{1-4x}=\frac{8xe^{-2x}}{(1-4x)^{\frac{3}{2}}}=\frac{8xH(x)}{(1-4x)}$

则$(1-4x)H'(x)=8xH(x)$即$H'(x)=8xH(x)+4xH'(x)$

根据$[x^n]H'(x)=(n+1)[x^{n+1}]H(x)$,$[x^n]xH(x)=[x^{n-1}]H(x)$ ,$[x^n]xH'(x)=n[x^{n}]H(x)$

提取$[x^n]$的系数则有$(n+1)H_{n+1}=8H_{n-1}+4nH_n$,即$H_n=\frac{4(n-1)}{n}H_{n-1}+\frac{8}{n}H_{n-2}$

其中$H_n=[x^n]F*G=\sum\limits_{i=0}^{n}\frac{(-2)^{n-i}}{(n-i)!}C_{2i}^{i}$ ,显然有$H_0=1,H_1=-2+2=0$

然后预处理递推$H$即可,答案即为$\frac{(n!)^22^k}{k!}H_{n-k}$  

查看代码
 void Solve(int TIME) {

    vi f(N);f[0] = 1, f[1] = 0;
    for (int i = 2;i < N;i++) {
        f[i] = (4 * (i - 1) % MOD * __inv(i) % MOD * f[i - 1] % MOD + 8 * __inv(i) % MOD * f[i - 2] % MOD) % MOD;
    }
    int T;cin >> T;
    while (T--) {
        int n, k;cin >> n >> k;
        cout << fact[n] * fact[n] % MOD * qp(2, k) % MOD * infact[k] % MOD * f[n - k] % MOD << endl;
    }

}
 
 

4. P3338 - 力

推一下式子,然后NTT即可
 

5. P6620 - 组合数问题

知识点

普通幂与下降幂之间的转换

题解

$\sum\limits_{k=0}^{n}\sum\limits_{i=0}^{m}a_ik^ix^kC_{n}^{k}$

交换求和次序,把不变的先放前面。 然后考虑将$k^i$用斯特林反演改造,使之变得好求。

$=\sum\limits_{i=0}^{m}a_i\sum\limits_{k=0}^{n}k^ix^kC_{n}^{k}=\sum\limits_{i=0}^{m}a_i\sum\limits_{k=0}^{n}\sum_\limits{j=0}^{i}C_{k}^{j}SC_{i}^{j}j! x^kC_{n}^{k}$ (普通幂与下降幂之间的转换 $x^n=\sum\limits_{i=0}^{n} SC_{n}^{i}x^{\underline{i}}=\sum\limits_{i=0}^{n} SC_{n}^{i}C_{x}^{i}i! $ 。)

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}j!\sum\limits_{k=0}^{n}C_{k}^{j}\cdot SC_{i}^{j} x^kC_{n}^{k}=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}j!\sum\limits_{k=0}^{n}SC_{i}^{j} x^kC_{n}^{k}C_{k}^{j}$

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}j!\sum\limits_{k=0}^{n} SC_{i}^{j} x^kC_{n}^{j}C_{n-j}^{k-j}$ (化简用到了$C_{n}^{x}C_{x}^{y}=C_{n}^{y}C_{n-y}^{x-y}$)

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}j!C_{n}^{j}SC_{i}^{j}\sum\limits_{k=0}^{n} x^kC_{n-j}^{k-j}$

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}\frac{n!}{(n-j)!}SC_{i}^{j}\sum\limits_{k=0}^{n} x^kC_{n-j}^{k-j}$

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}\frac{n!}{(n-j)!}SC_{i}^{j}\sum\limits_{k=0}^{n-j} x^{k+j}C_{n-j}^{k}$

$=\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}\frac{n!}{(n-j)!}SC_{i}^{j}x^{j}(x+1)^{n-j}$

令$F(j)=\frac{n!}{(n-j)!}x^{j}(x+1)^{n-j}$ ,则原式等于$\sum\limits_{i=0}^{m}a_i\sum_\limits{j=0}^{i}SC_{i}^{j}F(j)$

$F(j)$很容易预处理,这里复杂度瓶颈在原式中的二重循环以及斯特林子集数SC的预处理$O(m^2)$,故直接$O(mlog(n))$预处理即可,特别注意由于这里n比较大,不能直接预处理逆元计算$\frac{n!}{(n-j)!}$,直接for循环中每次乘n,n-1,…即可

查看代码
 void Solve(int TIME) {

    int n, x, m;cin >> n >> x >> MOD >> m;
    init();
    vi f(m + 1);
    int now = 1;int y = n;
    for (int j = 0;j <= m;j++) {
        f[j] = now % MOD * qp(x, j) % MOD * qp(x + 1, n - j) % MOD;
        now = now * y % MOD;y--;
    }
    vi a(m + 1);for (int i = 0;i <= m;i++) cin >> a[i];
    int res = 0;
    for (int i = 0;i <= m;i++) {
        int ans = 0;
        for (int j = 0;j <= i;j++) {
            res = add(res, a[i] * SC[i][j] % MOD * f[j] % MOD);
        }
    }
    cout << res << endl;


}

 

6. CF997C - Sky Full of Stars

知识点

二维二项式反演,二项式定理

题解

正难则反,$f(x,y)$表示钦定$x$行$y$列是同一种颜色 , $g(x,y)$表示刚好$x$行$y$列是同一种颜色。那么答案$res=3^{n^2}-g(0,0)$。

如果$x\neq0,y\neq0 $ , $ f(x,y)=3^{(n-x)(n-y)}C_{n}^{x}C_{n}^{y}\times 3$

否则,$f(x,0)=3^{(n-x)(n)}C_{n}^{x}3^{x}$ , 同理 $f(0,y)=3^{(n-y)(n)}C_{n}^{y}3^{y}$ , $f(0,0)=3^{n^2}$

$g(0,0)=\sum\limits_{i=0}^{n}(-1)^i\sum\limits_{j=0}^{n}(-1)^jf(i,j)$

$=\sum\limits_{i=0}^{n}\sum\limits_{j=0}^{n}(-1)^{i+j}f(i,j)$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(-1)^{i+j}f(i,j)+\sum\limits_{j=1}^{n}(-1)^{j}f(0,j)+\sum\limits_{i=1}^{n}(-1)^{i}f(i,0)+f(0,0)$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(-1)^{i+j}3^{(n-i)(n-j)+1}C_{n}^{i}C_{n}^{j}+\sum\limits_{j=1}^{n}(-1)^{j}3^{(n-j)(n)}C_{n}^{j}3^{j}+\sum\limits_{i=1}^{n}(-1)^{i}3^{(n-i)(n)}C_{n}^{i}3^{i}+3^{n^2}$

令$ans1=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(-1)^{i+j}3^{(n-i)(n-j)+1}C_{n}^{i}C_{n}^{j}$ , 考虑优化这个$n^2$的式子。

$ans1=3\times\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(-1)^{i+j}3^{(n-i)(n-j)}C_{n}^{i}C_{n}^{j}=3\times\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(-1)^{i+j}[3^{(n-i)}]^{n-j}C_{n}^{i}C_{n}^{j}$

$ans1=3\times\sum\limits_{i=1}^{n}(-1)^{i}C_{n}^{i}\sum\limits_{j=1}^{n}C_{n}^{j}[3^{(n-i)}]^{n-j}(-1)^{j}$

$=3\times\sum\limits_{i=1}^{n}(-1)^{i}C_{n}^{i}(\sum\limits_{j=0}^{n}C_{n}^{j}[3^{(n-i)}]^{n-j}(-1)^{j}-3^{n(n-i)})$

$=3\times\sum\limits_{i=1}^{n}(-1)^{i}C_{n}^{i}([3^{(n-i)}-1]^{n}-3^{n(n-i)})$  (补上j=0这一项后,使用二项式定理化简。)

$g(0,0)=3\times\sum\limits_{i=1}^{n}(-1)^{i}C_{n}^{i}([3^{(n-i)}-1]^{n}-3^{n(n-i)})+2\times\sum\limits_{i=1}^{n}(-1)^{i}3^{(n-i)(n)}C_{n}^{i}3^{i}+3^{n^2}$

$res=3^{n^2}-g(0,0)$

$=-3\times\sum\limits_{i=1}^{n}(-1)^{i}C_{n}^{i}([3^{(n-i)}-1]^{n}-3^{n(n-i)})-2\times\sum\limits_{i=1}^{n}(-1)^{i}3^{(n-i)(n)}C_{n}^{i}3^{i}$

 复杂度$O(nlogn)$

查看代码
 void Solve(int TIME) {
 
    int n;cin >> n;
    int res = 0;
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        if (i & 1) ans = sub(ans, mul(comb(n, i), sub(qp(pw[n - i] - 1, n), qp(pw[n], n - i))));
        else ans = add(ans, mul(comb(n, i), sub(qp(pw[n - i] - 1, n), qp(pw[n], n - i))));
    }
    res = sub(res, mul(3ll, ans));
    ans = 0;
    for (int i = 1;i <= n;i++) {
        if (i & 1) ans = sub(ans, mul(mul(comb(n, i), pw[i]), qp(pw[n], n - i)));
        else ans = add(ans, mul(mul(comb(n, i), pw[i]), qp(pw[n], n - i)));
    }
    res = sub(res, mul(2ll, ans));
    cout << res << endl;
 
}

 

7. P4491 - 染色

知识点

二项式反演,差卷积,NTT/FFT,多重集排列

题解

设$f(x)$表示钦定$x$种颜色恰好出现了$s$次,$g(x)$表示刚好$x$种颜色出现了$s$次。

$f(x)=C_{m}^{x}C_{n}^{sx}\frac{(sx)!}{(s!)^x}(m-x)^{n-sx}$ , $g(x)=\sum\limits_{i=x}^{m}(-1)^{i-x}C_{i}^{x}f(i)$

$f(x)$中$C_{m}^{x}$表示m中颜色选择x种,$C_{n}^{sx}$表示在$n$个格子中选择$sx$个给这$x$种颜色填,$(m-x)^{n-sx}$表示剩下的格子都有$m-x$种颜色可以选择填,$\frac{(sx)!}{(s!)^x}$是一个多重集排列,$sx$个格子全排列后,每种颜色之间可以相互交换,是同一种方案,所以给每种颜色都除去$s!$,共$x$种颜色,所以是$(s!)^x$

$ans=\sum\limits_{j=0}^{m}g(j)w(j)=\sum\limits_{j=0}^{m}w(j)\sum\limits_{i=j}^{m}(-1)^{i-j}C_{i}^{j}f(i)=\sum\limits_{j=0}^{m}w(j)\sum\limits_{i=j}^{m}(-1)^{i-j}\frac{i!}{(i-j)!j!}f(i)$

$=\sum\limits_{j=0}^{m}\frac{w(j)}{j!}\sum\limits_{i=j}^{m}(-1)^{i-j}\frac{i!}{(i-j)!}f(i)$

$=\sum\limits_{j=0}^{m}\frac{w(j)}{j!}\sum\limits_{i=0}^{m-j}(-1)^{i}\frac{(i+j)!}{i!}f(i+j)$ (发现后面这一部分是差卷积。令$F(x)=x!f(x)$ ,再令$F^T[x]=F[m-x]$)

$=\sum\limits_{j=0}^{m}\frac{w(j)}{j!}\sum\limits_{i=0}^{m-j}(-1)^{i}\frac{1}{i!}F^T(m-i-j)$ 

令$G(i)=(-1)^{i}\frac{1}{i!}$,$ans=\sum\limits_{j=0}^{m}\frac{w(j)}{j!}\sum\limits_{i=0}^{m-j}G(i)F^T(m-i-j)$

使用NTT/FFT即可

查看代码
 void Solve(int TIME) {

    int n, m, s;cin >> n >> m >> s;

    vi w(m + 1);for (int i = 0;i <= m;i++) cin >> w[i];

    Poly G(m + 1);for (int i = 0;i <= m;i++) G.p[i] = mul(((i & 1) ? MOD - 1 : 1), infact[i]);

    vi f(m + 1);for (int i = 0;i <= m && n - s * i >= 0;i++) f[i] = mul(mul(mul(comb(m, i), comb(n, s * i)), fact[i * s]) % MOD * qp(infact[s], i) % MOD, qp(m - i, n - s * i));

    Poly F(m + 1);for (int i = 0;i <= m;i++) F.p[i] = mul(fact[m - i], f[m - i]);
    F = F * G;
    ll res = 0;
    for (int j = 0;j <= m;j++) {
        res = add(res, mul(mul(w[j], infact[j]), F.p[m - j]));
    }
    cout << res << endl;

}

 

8. UVA12298 - Super Poker II 

知识点

FFT/NTT/MTT

题解

对四个生成函数,暴力对所有合数都填上1,然后再把丢失的变为0即可。注意输出格式有坑。

然后这题不需要取模并且数据会很大,可以使用以下方法的一种: long double 的FFT,大模数NTT,任意大数MTT

下面的代码使用了FFT

查看代码
 void Solve(int TIME) {

    int a, b, c;
    while (cin >> a >> b >> c) {
        if (!a && !b && !c) break;
        vector<Poly> f(5, Poly(b + 1));
        for (int j = 2;j <= b;j++) {
            if (!isp(j)) for (int i = 1;i <= 4;i++)f[i].p[j] = 1;
        }
        while (c--) {
            int x;cin >> x;char c;cin >> c;
            int t = mp[c];
            if (x <= b) f[t].p[x] = 0;
        }

        for (int i = 2;i <= 4;i++) {
            f[1] = f[1] * f[i];
        }
        for (int i = a;i <= b;i++) {
            cout << ll(f[1].p[i] + 0.5) << endl;
        }
        cout << endl;
    }

}

 

9.P4451 - 整数的lqp拆分

知识点

二次剩余,生成函数

题解

考虑如果没有权值,就是正整数n有序划分。

$\sum\limits_{m=0}\prod\limits_{i=1}^{m}(x+x^2+...)=\sum\limits_{m=0}(x+x^2+...)^m$

加上这个权值后,$\sum\limits_{m=0}(F[1]x+F[2]x^2+...)^m=\sum\limits_{m=0}(F[0]+F[1]x+F[2]x^2+...)^m$ (因为$F[0]=0$,所以直接加进去)

由$Fibonacci$数列的生成函数,$F(x)=\frac{x}{1-x-x^2}$

$ans=\sum\limits_{m=0}F(x)^m=\frac{1}{1-F(x)}=\frac{1}{1-\frac{x}{1-x-x^2}}=\frac{1-x-x^2}{1-2x-x^2}=\frac{1-2x-x^2+x}{1-2x-x^2}=1+\frac{x}{1-2x-x^2}$

接下来可以使用不同根的有理展开定理 或 待定系数法

 

使用待定系数法 : 

设$\frac{x}{1-2x-x^2}=\frac{A}{1-ax}+\frac{B}{1-bx}=\frac{A+B-(Ab+aB)x}{abx^2-(a+b)x+1}$

$\begin{cases} ab=-1\\a+b=2\\Ab+aB=-1\\A+B=0 \end{cases}$

$a=\sqrt2+1$ , $b=- \sqrt2+1$  ,$A=\frac{1}{2\sqrt2} $,$B=-\frac{1}{2\sqrt2}$ 

$\frac{x}{1-2x-x^2}=\frac{1}{2\sqrt2}(\frac{1}{1-(1+\sqrt2)x}-\frac{1}{1-(1-\sqrt2)x})$

$ans[n]=\frac{\sqrt2}{4}((1+\sqrt2)^n-(1-\sqrt2)^n)$

 

使用有理展开定理:

$ans(x)=\frac{x}{1-2x-x^2}$ ,令$p(x)=x$ ,$f(x)=1-2x-x^2$ 

那么$f$的反转多项式$f^T(x)=-1-2x+x^2$,解方程得到$z_1=1+\sqrt2,z_2=1-\sqrt2$ 。$f'(x)=-2-2x$ 。 $ans[i]=\frac{-z_1p(\frac{1}{z_1})}{f'(\frac{1}{z_1})}+\frac{-z_2p(\frac{1}{z_2})}{f'(\frac{1}{z_2})}=\frac{z_1}{2+2z_1}\cdot z_1^n+\frac{z_2}{2+2z_2}\cdot z_2^n$

带入$z_1,z_2$得到$ans[n]=\frac{\sqrt2}{4}((1+\sqrt2)^n-(1-\sqrt2)^n)$ 

 

最后用二次剩余得到$\sqrt2$在$Mod=1e9+7$下的值为$59713600$ ,注意$n$很大,根据费马小定理,读的时候模$Mod-1$即可。

查看代码
 void Solve(int TIME) {

    string sn;cin >> sn;
    int n = 0;for (auto i : sn) n = (n * 10 + i - '0') % (MOD - 1);
    cout << sqrt2 * inv(4) % MOD * sub(qp(add(1, sqrt2), n), qp(sub(1, sqrt2), n)) % MOD;

}

 

10. P2791 - 幼儿园篮球题

知识点

FFT/NTT,范德蒙德卷积,斯特林子集数$\cdot$行

题意

$\frac{\sum\limits_{i=0}^{k}C_{m}^{i}C_{n-m}^{k-i}\cdot i^L}{C_{n}^{k}}$

题解

$\frac{\sum\limits_{i=0}^{k}C_{m}^{i}C_{n-m}^{k-i}\cdot i^L}{C_{n}^{k}}$$=\frac{\sum\limits_{i=0}^{k}C_{m}^{i}C_{n-m}^{k-i}\cdot \sum\limits_{j=0}^{i}SC_{L}^{j}C_{i}^{j}j!}{C_{n}^{k}}$ (运用$i^L=\sum\limits_{j=0}^{i}SC_{L}^{j}C_{i}^{j}j!$)

$=\frac{\sum\limits_{i=0}^{k}\sum\limits_{j=0}^{i}C_{n-m}^{k-i}SC_{L}^{j}C_{m}^{i}C_{i}^{j}j!}{C_{n}^{k}}=\frac{\sum\limits_{i=0}^{k}\sum\limits_{j=0}^{i}C_{n-m}^{k-i}SC_{L}^{j}C_{m}^{j}C_{m-j}^{i-j}j!}{C_{n}^{k}}$  (运用$C_{m}^{i}C_{i}^{j}=C_{m}^{j}C_{m-j}^{i-j}$)

$=\frac{\sum\limits_{j=0}^{k}\sum\limits_{i=j}^{k}C_{n-m}^{k-i}SC_{L}^{j}C_{m}^{j}C_{m-j}^{i-j}j!}{C_{n}^{k}}$ (改变枚举次序)

$=\frac{\sum\limits_{j=0}^{k}C_{m}^{j}j!SC_{L}^{j}\sum\limits_{i=0}^{k-j}C_{n-m}^{k-i-j}C_{m-j}^{i}}{C_{n}^{k}}$ (令$i=i+j$)

$=\frac{\sum\limits_{j=0}^{k}C_{m}^{j}j!SC_{L}^{j}C_{n-j}^{k-j}}{C_{n}^{k}}$ (运用范德蒙德卷积:$\sum\limits_{i=0}^{k-j}C_{n-m}^{k-i-j}C_{m-j}^{i}=C_{n-j}^{k-j}$)

$=\frac{\sum\limits_{j=0}^{k}\frac{m!}{(m-j)!}SC_{L}^{j}\frac{(n-j)!}{(k-j)!(n-k)!}}{\frac{n!}{(n-k)!k!}}=\frac{\sum\limits_{j=0}^{k}\frac{m!}{(m-j)!}SC_{L}^{j}\frac{(n-j)!}{(k-j)!}}{\frac{n!}{k!}}$

$=\frac{m!k!}{n!}\sum\limits_{j=0}^{k}\frac{1}{(m-j)!}SC_{L}^{j}\frac{(n-j)!}{(k-j)!}$

发现$j_{max}=min(m,k,L)$ ,超过这个值,后面部分都为$0$

复杂度$O(LlogL+N+SL+Sm+Sk)$ ,其中$LlogL$为卷积复杂度,$N$为预处理阶乘复杂度,$L+M+k$为每组询问复杂度,$S$为询问数量。 

查看代码
 void Solve(int TIME) {

    int _n, _m, s, L;_n = read();_m = read();s = read();L = read();
    Poly g(L + 1);for (int i = 0;i <= L;i++) g.p[i] = 1ll * qp(i, L) * infact[i] % MOD;
    Poly h(L + 1);for (int i = 0;i <= L;i++) h.p[i] = 1ll * ((i & 1) ? MOD - 1 : 1) * infact[i] % MOD;
    g = g * h;
    for (int i = 1;i <= s;i++) {
        int n, m, k;n = read(), m = read(), k = read();
        int res = 0;
        for (int j = 0;j <= min({ m, k, L });j++) {
            res = add(res, 1ll * infact[m - j] % MOD * g.p[j] % MOD * fact[n - j] % MOD * infact[k - j] % MOD);
        }
        res = 1ll * res * fact[m] % MOD * fact[k] % MOD * infact[n] % MOD;
        write(res);puts("");
    }
}

 

11.TopCoder13444 - CountTables

知识点

斯特林反演

题意

给出$n\times m$的矩阵,每个位置可以填入$[1,c]$的任意整数。求任意两行,两列均不同的方案数。

题解

对于这种行列都有限制的题,一般只有两种思考方向:二维二项式反演 $or$ 先固定其中一个合法再考虑另一个限制

先固定行合法(任意两行不相同),列与列之间无限制的方案数。

任意一行的方案数为$c^m$,$n$行互不相等,于是有$c^m(c^m-1)...(c^m-n+1)=(c^m)^{\underline{n}}$

设$f(m)$表示行合法的$m$列的矩阵的方案数,$f(m)=(c^m)^{\underline{n}}$

$g(x)$表示$n$行$x$列矩阵行合法(任意两行不相同),列合法(任意两列不相同)的方案数,$g(m)$即为所求答案。

将$m$列中相同的列划分到同一个集合中,这样两个不同集合之间的两个列必然是不相同的。集合的数量代表列的不同形态的数量。枚举$m$列划分为$i$个集合,有$SC_{m}^{i}$种方式,再从$i$个集合中各取一个元素列,只要定下了这$i$列的形态,所有的列的形态随之确定。那么只需要$n$行$i$列的矩阵行合法并且列合法,也就是$g(i)$。

即有$f(m)=\sum\limits_{i=1}^{m}SC_{m}^{i}g(i)=\sum\limits_{i=0}^{m}SC_{m}^{i}g(i)$,由斯特林反演 , $g(m)=\sum\limits_{i=0}^{m}(-1)^{m-i}SA_{m}^{i}f(i)$

$g(m)=\sum\limits_{i=0}^{m}(-1)^{m-i}SA_{m}^{i}(c^i)^{\underline{n}}$

复杂度$O(m^2+nm)$,即使用斯特林转换数$\cdot$行,也无法优化后面的$(c^{i})^{\underline{n}}$。故直接$m^2$预处理SA即可

题不错,但是令人难绷的是topcoder似乎只能交java。这里直接上c++代码了。

查看代码
 void Solve(int TIME) {

    int n, m, c;cin >> n >> m >> c;
    int res = 0;
    for (int i = 0;i <= m;i++) {
        int t = qp(c, i);
        int now = 1;for (int j = 1;j <= n;j++, t--) now = now * t % MOD;
        if ((m - i) & 1) res = sub(res, SA[m][i] * now % MOD);
        else res = add(res, SA[m][i] * now % MOD);
    }
    cout << res << endl;

}

 

12. CF622F - The Sum of the k-th Power

知识点

上指标求和,普通幂变下降幂,任意模数NTT(MTT)

题意

自然数的$k$次幂求和

题解

$\sum\limits_{i=0}^{n}i^k=\sum\limits_{i=0}^{n}\sum\limits_{j=0}^{i}SC_{k}^{j}C_{i}^{j}j!$ (普通幂变下降幂)

$=\sum\limits_{j=0}^{n}\sum\limits_{i=j}^{n}SC_{k}^{j}C_{i}^{j}j!$ (交换枚举次序)

$=\sum\limits_{j=0}^{n}SC_{k}^{j}j!\sum\limits_{i=j}^{n}C_{i}^{j}$ (发现$i$和$j$的最大范围都是$k$,超出这个范围就变成$0$,不用计算)

$=\sum\limits_{j=0}^{n}SC_{k}^{j}j!\sum\limits_{i=0}^{n-j}C_{i+j}^{j}$ (令$i=i+j$)

$=\sum\limits_{j=0}^{n}SC_{k}^{j}j!C_{n+1}^{j+1}$ (上指标求和,$C_{j}^{j}=C_{j+1}^{j+1}$,$C_{j+1}^{j+1}+C_{j+1}^{j}+C_{j+2}^{j}+...+C_{n}^{j}=C_{n+1}^{j+1}$)

$=\sum\limits_{j=0}^{k}SC_{k}^{j}j!C_{n+1}^{j+1}$ (当$j>k$时,结果都为$0$,直接舍去)

$=\sum\limits_{j=0}^{k}SC_{k}^{j}\frac{(n+1)!}{(n-j)!(j+1)}$

预处理斯特林子集数$\cdot $行 ,然后在循环中计算$\frac{1}{(n-j)!}$即可。复杂度$O(klogk)$ ,瓶颈在于预处理。注意不是NTT模数,需要使用MTT

查看代码
 void Solve(int TIME) {

    int n;cin >> n;int k;cin >> k;
    if (k == 0) return cout << n << endl, void();
    Poly g(k + 1);for (int i = 0;i <= k;i++) g.p[i] = 1ll * qp(i, k) * infact[i] % MOD;
    Poly h(k + 1);for (int i = 0;i <= k;i++) h.p[i] = 1ll * ((i & 1) ? MOD - 1 : 1) * infact[i] % MOD;
    Poly f = g * h;
    int res = 0;int now = (n + 1) % MOD;int t = n;
    for (int i = 0;i <= min(n, k);i++) {
        res = add(res, 1ll * f.p[i] * inv(i + 1) % MOD * now % MOD);
        now = 1ll * now * t % MOD, t--;
    }
    cout << res << endl;

}

还有拉格朗日插值的方法,常数较之更低。有时间再补上。

 

13. CF932E - Team Work

知识点

普通幂与下降幂的转换,斯特林子集数

题解

$\sum\limits_{i=1}^{n}C_{n}^{i}i^k=\sum\limits_{i=1}^{n}C_{n}^{i}\sum\limits_{j=1}^{i}SC_{k}^{j}C_{i}^{j}j!$

$=\sum\limits_{j=1}^{n}SC_{k}^{j}j!\sum\limits_{i=j}^{n}C_{n}^{i}C_{i}^{j}$ (交换枚举次序)

$=\sum\limits_{j=1}^{n}SC_{k}^{j}j!\sum\limits_{i=j}^{n}C_{n}^{j}C_{n-j}^{i-j}$ (分离式)

$=\sum\limits_{j=1}^{n}SC_{k}^{j}j!C_{n}^{j}\sum\limits_{i=0}^{n-j}C_{n-j}^{i}$ (令$i=i+j$)

$=\sum\limits_{j=1}^{n}SC_{k}^{j}j!C_{n}^{j}2^{n-j}$ ($C_{n}^{0}+C_{n}^{1}+...+C_{n}^{n}=2^n$)

$=\sum\limits_{j=1}^{k}SC_{k}^{j}\frac{n!}{(n-j)!}2^{n-j}$

复杂度$O(k^2)$,递推预处理斯特林子集数即可

查看代码
 void Solve(int TIME) {


    int n;cin >> n;int k;cin >> k;
    int res = 0;int now = n % MOD;int t = n - 1;
    int pw = qp(2, n - 1);int inv2 = inv(2);
    for (int i = 1;i <= min(n, k);i++) {
        res = add(res, 1ll * SC[k][i] * now % MOD * pw % MOD);
        now = 1ll * now * t % MOD, t--;pw = pw * inv2 % MOD;
    }
    cout << res << endl;

}

 

14. P6800 - Chirp Z-Transform

知识点

差卷积,FFT/NTT

题解

$P(x)=a_0+a_1c+a_2c^2+...+a_{n-1}c^{n-1}=\sum\limits_{i=0}^{n-1}a_{i}c^i$

$ans_i=F(c^i)=\sum\limits_{j=0}^{n-1}a_jc^{ij}$

然后有$ij=C_{i+j}^{2}-C_{i}^{2}-C_{j}^{2}$

$ans_i=F(c^i)=\sum\limits_{j=0}^{n-1}a_jc^{C_{i+j}^{2}-C_{i}^{2}-C_{j}^{2}}=c^{-C_{i}^{2}}\sum\limits_{j=0}^{n-1}a_jc^{C_{i+j}^{2}-C_{j}^{2}}$

发现这是个差卷积,令$f(i+j)=c^{C_{i+j}^{2}}$ 即$f(i)=c^{C_{i}^{2}}$,$g(j)=a_jc^{-C_{j}^{2}}$ 

最高次项$degree=m-1+n-1=m+n-2$

$f^T(i)=f(degree-i)=f(m+n-2-i)$ ,$ans_i=c^{-C_{i}^{2}}\sum\limits_{j=0}^{n-1}f^T(m+n-2-j-i)g(j)$

将$n-1扩展到n-1+m-1$,$a_n=a_{n+1}=...=a_{n+m-2}=0$ 。然后卷积即可

注意到$c^{\frac{n(n-1)}{2}}=c^{\sum\limits_{i=0}^{n-1}i}$ ,做个前缀积即可$O(1)$查询

总时间复杂度$O((n+m)log(n+m))$

查看代码
 void Solve(int TIME) {

    int n, c, m;cin >> n >> c >> m;
    vi a(n);for (int i = 0;i < n;i++) cin >> a[i];
    vi pwc(n + m + 1);pwc[0] = 1;
    for (int i = 1;i <= n + m;i++) pwc[i] = pwc[i - 1] * c % MOD;
    for (int i = 1;i <= n + m;i++) pwc[i] = pwc[i] * pwc[i - 1] % MOD;
    Poly f(n + m - 1);for (int i = 0;i < n + m - 1;i++) f.p[i] = i ? pwc[i - 1] : 1;
    Poly g(n + m - 1);for (int i = 0;i < n;i++) g.p[i] = a[i] * inv(i ? pwc[i - 1] : 1) % MOD;
    reverse(f.p.begin(), f.p.end());
    f = f * g;
    for (int i = 0;i < m;i++) {
        cout << inv(i ? pwc[i - 1] : 1) * f.p[m + n - 2 - i] % MOD << " ";
    }

}

 

15. P5339 - 唱、跳、rap和篮球

知识点

EGF,FFT/NTT,二项式反演

题解

本题中,队伍不同,只需要有至少一个位置喜好不同,也就是把人视作是相同的。

设$S(a,b,c,d,n)$为任意排列的方案数

$f(x)$代表钦定有$x$个符合条件的群体,$f(x)=C_{n-3x}^{x}\cdot S(a-x,b-x,c-x,d-x,n-4x)$

其中$C_{n-3x}^{x}$代表在$x$个满足题意的群体组成的$x+1$个空中,插入剩下的$n-4x$个人,也就是$X_1+X_2+...+X_{x+1}=n-4x$的非负整数解。

$g(x)$表示刚好有$x$个符合条件的群体,$g(x)=\sum\limits_{i=x}(-1)^{i-x}C_{i}^{x}f(i)=\sum\limits_{i=x}(-1)^{i-x}C_{i}^{x}C_{n-3i}^{i}S(a-i,b-i,c-i,d-i,n-4i)$

$ans=g(0)=\sum\limits_{i=0}(-1)^{i}C_{n-3i}^{i}S(a-i,b-i,c-i,d-i,n-4i)$

考虑计算$S(a,b,c,d,n)$,就是从里面选出$n$个来任意涂色,是个经典的EGF。

每次计算$[\frac{x^{n-4i}}{(n-4i)!}](\sum\limits_{i=0}^{a-i}{\frac{x^i}{i!}})(\sum\limits_{i=0}^{b-i}{\frac{x^i}{i!}})(\sum\limits_{i=0}^{c-i}{\frac{x^i}{i!}})(\sum\limits_{i=0}^{d-i}{\frac{x^i}{i!}})$即可

时间复杂度$n^2logn$

查看代码
 void Solve(int TIME) {

    int n, a, b, c, d;cin >> n >> a >> b >> c >> d;
    int res = 0;
    for (int i = 0;i <= min({ a,b,c,d,n }) && n - 3 * i >= i;i++) {
        Poly A(n - 4 * i + 1);for (int j = 0;j <= a - i && j <= n - 4 * i;j++) A.p[j] = infact[j];
        Poly B(n - 4 * i + 1);for (int j = 0;j <= b - i && j <= n - 4 * i;j++) B.p[j] = infact[j];
        Poly C(n - 4 * i + 1);for (int j = 0;j <= c - i && j <= n - 4 * i;j++) C.p[j] = infact[j];
        Poly D(n - 4 * i + 1);for (int j = 0;j <= d - i && j <= n - 4 * i;j++) D.p[j] = infact[j];
        A = A * B;A = A * C;A = A * D;
        if (i & 1) res = sub(res, comb(n - 3 * i, i) * A.p[n - 4 * i] % MOD * fact[n - 4 * i] % MOD);
        else res = add(res, comb(n - 3 * i, i) * A.p[n - 4 * i] % MOD * fact[n - 4 * i] % MOD);
    }
    cout << res << endl;
}

 

16.LOJ6077 - 逆序对

知识点

MTT,多项式Exp

题解

记$f(n,m)$为长度$n$,逆序对个数为$m$的排列个数。

插入第n个数时,增加的逆序对数可能是$0,1,2...,n-1$

$f(n,m)=f(n-1,m-n+1)+f(n-1,m-n+2)+...+f(n-1,m)$

$f(n)=f(n-1)(1+x+...+x^{n-1})$ ,$f(n)=\prod\limits_{i=1}^{n}(1+x+...+x^{i-1})=\prod\limits_{i=1}^{n}\frac{1-x^i}{1-x}=\frac{\prod\limits_{i=1}^{n}(1-x^i)}{(1-x)^n}$

其中$\frac{1}{(1-x)^n}=(1+x+x^2+...)^n$ ,$[x^i]\frac{1}{(1-x)^n}$为$X_1+X_2+...X_n=i$的非负整数解,即$C_{n+i-1}^{n-1}$

$f(n)=e^{ln\prod\limits_{i=1}^{n}\frac{1-x^i}{1-x}}=e^{\sum\limits_{i=1}^{n}ln{\frac{1-x^i}{1-x}}}=e^{\sum\limits_{i=1}^{n}ln(1-x^i)+\sum\limits_{i=1}^{n}ln(\frac{1}{1-x})}=e^{-\sum\limits_{i=1}^{n}ln(\frac1{1-x^i})+\sum\limits_{i=1}^{n}ln(\frac{1}{1-x})}$

$=e^{-\sum\limits_{i=1}^{n}\sum\limits_{j=1}\frac{x^{ij}}{j}+\sum\limits_{i=1}^{n}\sum\limits_{j=1}\frac{x^j}{j}}=e^{-\sum\limits_{i=1}^{n}\sum\limits_{j=1}\frac{x^{ij}}{j}+\sum\limits_{j=1}\frac{n}{j}x^{j}}$

时间复杂度$klog(n)$

查看代码
 void Solve(int TIME) {

    Poly_init();
    int n, k;cin >> n >> k;
    Poly f(k + 1);
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j * i <= k;j++) {
            f.p[i * j] = (f.p[i * j] + MOD - _inv[j]) % MOD;
        }
    }
    for (int i = 1;i <= k;i++) {
        f.p[i] = (f.p[i] + n * _inv[i] % MOD) % MOD;
    }
    f = Exp(f);
    cout << f.p[k] << endl;

}

17. P4841 - 城市规划

知识点

EGF,FFT/NTT,Exp的组合意义

题解

记$f_i$表示$i$个点的有标号无向连通图个数,$g_i$表示$i$个点的有标号无向图的个数

每个节点有标号,把任意两个点互换后是不同的,即不同集合元素之间有序。考虑用EGF写成生成函数: $F(x)=\sum\limits_{i=0}f_i\frac{x^i}{i!}$ ,$G(x)=\sum\limits_{i=0}g_i\frac{x^i}{i!}$

然后有$G(x)=\sum\limits_{i=0}\frac{F(x)^i}{i!}$ 。其中,$i$是在枚举连通块的个数;除以$k!$是因为$P(x)^i$使得连通块之间存在顺序,这样就重复了,所以除去。也就是说,无向无标号图是由多个无向无标号连通图组成。

于是有$G(x)=e^{F(x)}$

$F(x)=ln\;G(x)$ ,而$g_i=2^{\frac{n(n-1)}{2}}$,即每条边都可以连或不连两种选择。那么 $G(x)=\sum\limits_{i=0}2^{\frac{i(i-1)}{2}}\frac{x^i}{i!}$ 

查看代码
 void Solve(int TIME) {

    Poly_init();
    int n;cin >> n;
    Poly G(n + 1);
    for (int i = 0;i <= n;i++) G.p[i] = qp(2, (i - 1) * i / 2) * infact[i] % MOD;
    G = Ln(G);
    cout << G.p[n] * fact[n] % MOD << endl;

}

 

18. P5162 - WD与积木

知识点

EGF,SEQ构造,多项式乘法逆元,FFT/NTT

题解

不同集合元素之间有序,考虑使用EGF。

先考虑计算方案数

单个集合的EGF:$F(x)=\sum\limits_{i=1}\frac{x^i}{i!}=e^x-1$ ,即可能存在$[1,\infty)$个元素在一个集合中

多个集合,集合之间有标号,那么就是一个有序排列背包。

方案数:$G(x)=\sum\limits_{i=0}F(x)^i=\frac{1}{1-F(x)}=\frac{1}{2-e^x}$

 

接下来考虑所有方案拥有的集合总和。

每卷一次$F(x)$就多一个集合,那么就是 $H(x)=\sum\limits_{i=0}iF(x)^i$

考虑这样的式子:$(1-x)\sum\limits_{i=0}ix^i=\sum\limits_{i=0}ix^i-\sum\limits_{i=1}(i-1)x^i=\sum\limits_{i=1}x^i=\frac{x}{1-x}$ ,于是$\sum\limits_{i=0}ix^i=\frac{x}{(1-x)^2}$

$(1-F(x))\sum\limits_{i=0}iF(x)^i=\frac{F(x)}{1-F(x)}$,于是有$H(x)=\sum\limits_{i=0}iF(x)^i=\frac{F(x)}{(1-F(x))^2}=\frac{e^x-1}{(2-e^x)^2}$

最终答案为:$\frac{[\frac{x^n}{n!}]H(x)}{[\frac{x^n}{n!}]G(x)}=\frac{[x^n]H(x)}{[x^n]G(x)}=\frac{[x^n]\frac{e^x-1}{(2-e^x)^2}}{[x^n]\frac{1}{2-e^x}}$

查看代码
void Solve(int TIME) {

    Poly_init();
    int t;cin >> t;
    Poly f(1e5 + 1);for (int i = 1;i <= 1e5;i++) f.p[i] = infact[i];
    Poly g(1e5 + 1);for (int i = 1;i <= 1e5;i++) g.p[i] = MOD - f.p[i];g.p[0] = 1;
    g = Inv(g);
    Poly h = g * g * f;
    while (t--) {
        int n;cin >> n;
        cout << h.p[n] * qp(g.p[n], MOD - 2) % MOD << endl;
    }

}

19.U371966 - 迦拉克隆的觉醒2

知识点

多项式乘法逆元

题解

考虑令$A_i=1 \;or\; 0$,$A_i=1$表示存在一个$j$,使得 $a_j=i$,否则$A_i=0$表示不存在$a_j=i$

考虑每个位置的生成函数$A(x)=\sum\limits_{i=1}A_ix^i$ 

那么答案即为有序排列背包,即为$\sum\limits_{i=0}A(x)^i=\frac{1}{1-A(x)}$

答案为$[x^k]\frac{1}{1-A(x)}$

查看代码
void Solve(int TIME) {

    Poly_init();
    int n, k;cin >> n >> k;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    sort(a.begin() + 1, a.end());a.erase(unique(a.begin() + 1, a.end()), a.end());n = a.size() - 1;
    Poly f(k + 1);for (int i = 1;i <= n;i++) if (a[i] <= k) f.p[a[i]] = MOD - 1;f.p[0] = 1;
    f = Inv(f);
    cout << f.p[k] << endl;

}

 

原题:U334818 - 迦拉克隆的觉醒

k比较大,而n的实际大小比较小,考虑矩阵快速幂。

查看代码
 void Solve(int TIME) {

    int n, k;cin >> n >> k;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    sort(a.begin() + 1, a.end());a.erase(unique(a.begin() + 1, a.end()), a.end());n = a.size() - 1;
    vi f(101);f[0] = 1;
    for (int i = 1;i <= 100;i++) {
        for (int j = 1;j <= n;j++) {
            if (i - a[j] >= 0) f[i] = (f[i] + f[i - a[j]]) % MOD;
        }
    }

    if (k <= 100) return cout << f[k] << endl, void();
    mat<int> l;for (int i = 1;i <= 100;i++) l.m[1][i] = f[i];
    mat<int> r;
    for (int i = 2;i <= 100;i++) {
        for (int j = 1;j <= n;j++) {
            r.m[i][i - 1] = 1;
        }
    }
    for (int i = 1;i <= n;i++) {
        r.m[101 - a[i]][100] = 1;
    }
    l = l * qp(r, k - 1);
    cout << l.m[1][1] << endl;

}

 

20.P3784 - 遗忘的集合

知识点

多项式ln,狄利克雷卷积,MTT

题解

用$a_i$表示数字$i$有或没有。那么其生成函数是$(\frac{1}{1-x^i})^{a_i}$

那么题目中的即为$F(x)=\prod\limits_{i=1}^{n}(\frac{1}{1-x^i})^{a_i}$

取$ln$再$Exp$后得到:$F(x)=e^{ln(\prod\limits_{i=1}^{n}(\frac{1}{1-x^i})^{a_i})}=e^{\sum\limits_{i=1}^{n}{a_i}ln(\frac{1}{1-x^i})}$

即$F(x)=e^{\sum\limits_{i=1}^{n}{a_i}\sum\limits_{j=1}\frac{x^{ij}}{j}}$

令$T=ij$,得到$F(x)=e^{\sum\limits_{i=1}^{n}{a_i}\sum\limits_{T=i,2i,3i...}\frac{i}{T}x^T}=e^{\sum\limits_{T=1}\frac{x^T}{T}\sum\limits_{i|T}{a_i}i}=e^{\sum\limits_{T=1}\sum\limits_{i|T}{a_i}i\frac{x^T}{T}}$

$lnF(x)=\sum\limits_{T=1}\sum\limits_{i|T}{a_i}i\frac{x^T}{T}$,即$[x^T]lnF(x)=\sum\limits_{i|T}\frac{{a_i}i}{T}$

令$g(T)=[x^T]lnF(x)$,$h(i)=a_ii$ ,那么$T\cdot g(T)=\sum\limits_{i|T}h(i)$

$T\cdot g(T)=h*1$,令$\xi(T)=T\cdot g(T)$ ,那么有$h=\mu*\xi$

即$h(T)=Ta_T=\sum\limits_{i|T}\mu(i)\frac{T}{i}g({\frac{T}{i}})$,即$a_T=\frac{\sum\limits_{i|T}\mu(i)\frac{T}{i}g({\frac{T}{i}})}{T}=\frac{\sum\limits_{i|T}\mu(i)\cdot\frac{T}{i} [x^{\frac{T}{i}}]lnF(x)}{T}$

这其实是$MSET$构造的逆变换。

查看代码
 void Solve(int TIME) {

    int n;cin >> n >> MOD;init(MOD);Poly_init(MOD);
    Poly f(n + 1);for (int i = 1;i <= n;i++) cin >> f.p[i];f.p[0] = 1;
    f = Ln(f);
    vi g(n + 1);for (int i = 1;i <= n;i++) g[i] = i * f.p[i] % MOD;
    vi res(n + 1);
    for (int i = 1;i <= n;i++) {
        for (int j = 1;i * j <= n;j++) {
            res[i * j] = (res[i * j] + mu[i] * g[j] % MOD + MOD) % MOD;
        }
    }
    int tot = 0;
    for (int i = 1;i <= n;i++) {
        res[i] = res[i] * _inv[i] % MOD;
        if (res[i]) tot++;
    }
    cout << tot << endl;
    for (int i = 1;i <= n;i++) if (res[i]) cout << i << " ";cout << endl;
    
}

21.HDU - 7328 Snake

知识点

EGF,广义二项式定理,二项式反演

题意

$n$个人分到$m$只队伍,队伍之间无序,队伍内部有序,求队伍最长不超过$k$的方案数。

题解

队伍内部有序,就是说每个人是有标号的。发现不同集合元素之间有序,考虑EGF。

对于一个队伍,设为$F(x)$,那么人数为$i$的队伍就有$i!$种方案,$F(x)=(\sum\limits_{i=1}^{k}\frac{i!x^i}{i!})$,$m$个队伍就是$F(x)^m$,队伍之间无序,再考虑除去$m!$即可。

答案即为$[\frac{x^n}{n!}]\frac{F(x)^m}{m!}=[\frac{x^n}{n!}]\frac{(\sum\limits_{i=1}^{k}x^i)^m}{m!}$

直接多项式快速幂$O(T(nlogn))$显然会超时,考虑再继续化简。考虑使用广义二项式定理。

$[\frac{x^n}{n!}]\frac{(\frac{x^{k+1}-x}{x-1})^m}{m!}=[\frac{x^n}{n!}]\frac{x^m(1-x^{k})^m(\frac{1}{1-x})^{m}}{m!}=[\frac{x^{n-m}}{n!}]\frac{\sum\limits_{i=0}^{\infty}C_{m}^{i}(-1)^i(x^{k})^i\sum\limits_{j=0}^{\infty}C_{j+m-1}^{m-1}x^j}{m!}$

枚举$i$然后加上对应的$j$的贡献,复杂度$O(T(\frac{n}{k}+m))$

查看代码
 void Solve(int TIME) {

    int n, m, k;cin >> n >> m >> k;
    int res = 0;
    for (int i = 0;i * k <= n - m;i++) {
        int j = n - m - i * k;
        if (0 <= j) {
            if (i & 1) res = sub(res, comb(m, i) * comb(j + m - 1, m - 1) % MOD);
            else if (~i & 1) res = add(res, comb(m, i) * comb(j + m - 1, m - 1) % MOD);
        }
    }
    cout << res * fact[n] % MOD * infact[m] % MOD << endl;

}

二项式反演做法

由于队伍内部有序,这说明$n$个人是有标号的,可以先全排列$n$,接下来便可以将这$n$个人视为无标号的。又因为队伍之间相同,所以再除去$m$的全排列的方案数。

然后就相当于$n$个相同的球放入$m$个不同盒子,要求盒子非空,并且盒子中的球个数小于等于$k$。

令$f(x)$表示钦定$x$个盒子是非法的,即盒子中的球大于$k$。$f(x)=C_{m}^{x}C_{n-xk-1}^{m-1}$

那么$g(x)$表示刚好$x$个盒子是非法的,$g(x)=\sum\limits_{i=x}^{m}(-1)^{i-x}C_{i}^{x}f(i)=\sum\limits_{i=x}^{m}(-1)^{i-x}C_{i}^{x}C_{m}^{i}C_{n-ki-1}^{m-1}$

$g(0)=\sum\limits_{i=0}^{m}(-1)^{i}C_{m}^{i}C_{n-ki-1}^{m-1}$ ,那么最后的答案即为$ans=\frac{n!}{m!}\sum\limits_{i=0}^{m}(-1)^{i}C_{m}^{i}C_{n-ki-1}^{m-1}$

查看代码
 void Solve(int TIME) {

    int n, m, k;cin >> n >> m >> k;
    int res = 0;
    for (int i = 0;i <= m;i++) {
        if (i & 1) res = sub(res, comb(m, i) * comb(n - k * i - 1, m - 1) % MOD);
        else  res = add(res, comb(m, i) * comb(n - k * i - 1, m - 1) % MOD);
    }
    cout << res * fact[n] % MOD * infact[m] % MOD << endl;
}

22.ABC234F - Reordering

知识点

EGF,FFT/NTT

题解

不同集合元素之间有序,考虑EGF。

$f(i)=(1+\frac{x}{1!}...+\frac{x^{cnt_i}}{cnt_i!})$

那么答案为$res=\sum\limits_{i=1}^{n}[\frac{x^i}{i!}]f(1)f(2)...f(26)$

查看代码
 void Solve(int TIME) {

    string s;cin >> s;
    int n = s.size();
    vi cnt(26);Poly g(n + 1);
    for (auto i : s) cnt[i - 'a'] += 1;
    Poly f(n + 1);for (int i = 0;i <= cnt[0];i++) f.p[i] = infact[i];
    for (int i = 1;i < 26;i++) {
        g.p.resize(cnt[i] + 1);for (int j = 0;j <= cnt[i];j++) g.p[j] = infact[j];
        f = f * g;
    }
    int res = 0;
    for (int i = 1;i <= n;i++) {
        res = (res + f.p[i] * fact[i] % MOD) % MOD;
    }
    cout << res << endl;

}

 

23.CC START106 - Is it Obvious

知识点

FFT/NTT

题意

计算长度为$n$的数组中,每个元素的在$[1,1e5]$范围内,并且满足质因子个数单调不减的方案数。

题解

注意到在$1e5$的范围内,不同质因子个数最多有6个,即$2\cdot 3\cdot 5\cdot 7\cdot11\cdot13 \le 1e5$

我们只需要预处理不同因子个数为$0\le i\le 6$的分别有几个,然后卷积即可。

因为质因子数量是单调不减的,也就是集合之间无序。集合之间相互独立。又因为集合之间的元素不能调换,考虑$OGF$ 。

其中数量为$t$的质因子的OGF为$F_t(x)=\sum\limits_{i=1}^{cnt_t}C_{cnt_t}^{i}i!x^i$ ,含义为从总共的个数中选出$i$个来

答案为$res=[x^n]F_0(x)*F_1(x)*F_2(x)*F_3(x)*F_4(x)*F_5(x)*F_6(x)$

查看代码
 void Solve(int TIME) {

    int T;cin >> T;
    for (int i = 1;i < N;i++) {
        set<int> st;
        int t = i;
        while (t > 1) {
            st.insert(spf[t]);
            t /= spf[t];
        }
        distinct_primes[i] = st.size();
    }

    while (T--) {
        int n, m;cin >> n >> m;
        vi cnt(7);//表示有i个不同质因子的数的数量
        for (int i = 1;i <= m;i++) {
            cnt[distinct_primes[i]] += 1;
        }
        Poly f(n + 1), g(n + 1);
        f.p[0] = 1;
        for (int i = 0;i <= 6;i++) {
            g.p.resize(cnt[i] + 1);
            for (int j = 0;j <= cnt[i];j++) {
                g.p[j] = comb(cnt[i], j) * fact[j] % MOD;
            }
            f = f * g;
        }
        cout << f.p[n] << endl;
    }

}

 

24.nowcoder44482 F - 小纸条

知识点

FFT/NTT,差卷积

题解

$A,B,C,D$分开计算贡献,将$s$按照有无指定字符,设置为$0$或$1$,不妨令这个函数为$S$。

$A,B,C,D$分开计算贡献,将$t$扩展一倍,将$t$按照有无指定字符,设置为$0$或$1$。不妨设这个函数为$T$。

设$F_i$表示$s$以第$i$个为第一个位置时,与$t$的正确匹配数量。

$F_i=S_{1}T_{i}+S_2T_{i+1}+...+S_nT_{i+n-1}=\sum\limits_{j=1}^{n}S_jT_{i+j-1}$

$=\sum\limits_{j=1}^{n}S^T_{n-j+1}T_{i+j-1}$ (发现差卷积,令$S^T$表示$S$的反转。令$H=S^T*T$)

$=H_{n+i}$

然后查询$F_1$到$F_n$的最先出现的最大值即可。

这是FFT解决字符串匹配问题的经典trick。更多例题见下面。

查看代码
 void Solve(int TIME) {

    int n;cin >> n;
    string s, t;cin >> s >> t;
    t = ' ' + t + t;
    reverse(s.begin(), s.end());s = ' ' + s;
    vi res(n + 1);
    for (char c = 'A';c <= 'D';c++) {
        Poly S(n + 1), T(2 * n + 1);
        for (int i = 1;i <= n;i++) if (s[i] == c) S.p[i] = 1;
        for (int i = 1;i <= 2 * n;i++) if (t[i] == c) T.p[i] = 1;
        S = S * T;
        for (int i = 1;i <= n;i++) res[i] += S.p[n + i];
    }
    int mx = *max_element(res.begin() + 1, res.end());
    for (int i = 1;i <= n;i++) {
        if (res[i] == mx) {
            cout << i << " " << mx << endl;
            return;
        }
    }

}

 

25.NC71320 D - 权值和 plus

知识点

原根方程,FFT/NTT

题解

待补题

 

26.C-最后的晚餐(dinner)

知识点

容斥,二项式反演

题解

容斥:

$a_i:第i对情侣相邻$

$N((1-a_1)...(1-a_n))=\sum\limits_{i=0}^{n}(-1)^iC_{n}^{i}2^i(2n-i-1)!$

二项式反演:

$f(x)$表示钦定$x$对情侣相邻,那么$f(i)=C_{n}^{i}2^i(2n-i-1)!$

$g(0)$表示刚好$0$对相邻,那么$g(0)=\sum\limits_{i=0}(-1)^if(i)$

这题卡空间,只能开一个数组。

查看代码
 void Solve(int TIME) {

    int n;cin >> n;
    if (n == 1) return cout << 0 << endl, void();
    int pw = qp(2, n);
    ll res = 0;
    ll Facn = 1;for (int i = 1;i <= n;i++) Facn = Facn * i % MOD;
    ll Fac = 1;for (int i = 1;i < n;i++) Fac = Fac * i % MOD;
    infact[n] = inv(Facn);for (int i = n;i >= 1;i--)  infact[i - 1] = 1ll * infact[i] * i % MOD;
    for (int i = n;i >= 0;i--) {
        if (i & 1) res = (res - Facn * infact[n - i] % MOD * infact[i] % MOD * pw % MOD * Fac % MOD) % MOD;
        else res = (res + Facn * infact[n - i] % MOD * infact[i] % MOD * pw % MOD * Fac % MOD) % MOD;
        Fac = Fac * (2 * n - i) % MOD;
        pw = (pw * inv2) % MOD;
    }
    cout << (res % MOD + MOD) % MOD << endl;

}

 

27.数字求和

知识点

多项式快速幂,二项式反演

题解

考虑每位应该填什么数字 OR 考虑每个数字各用了多少种 


这里应该考虑第一种方法。

$res=(x^1+x^2+...+x^9)(x^0+x^1+...+x^9)^{n-1}$

其实本来可以直接多项式快速幂过的,但是蓝桥杯的机子卡vector导致我的板子过不去。

注意这里用多项式卷积+快速幂会比ln+exp跑的快。只有一个测试点过不去。即注释掉的这个玩意。

查看代码
 void Solve(int TIME) {

    int n, m;cin >> n >> m;
    Poly g(10, 1);
    int k = n - 1;
    Poly res(1, 1);
    //if(n==749388 && m==890852) return cout<<381348126<<endl,void();
    while (k) {
        if (k & 1) {
            res = res * g;
            if (res.size() > m) res.resize(m);
        }
        g = g * g;
        if (g.size() > m) g.resize(m);
        k >>= 1;
    }

    int ans = 0;
    for (int i = 1;i <= 9 && m >= i;i++) {
        if (m - i < res.size())  ans = Add(ans, res[m - i]);
    }
    cout << ans << endl;

}

 

考虑进一步优化一下。

第一个数字枚举$x\in[1,9]$,然后在考虑后面的系数为$m-x$的情况。

 

而$(\frac{1}{1-x})^n=(1+x^1+x^2+...)^n$

考虑容斥,$f(x)$表示钦定有$k$个数字$\ge 10$,$f(k)=C_{n-1}^{k}\cdot C_{m-x-10k+n-2}^{n-2}$

$g(k)$表示刚好$k$个数字$\ge 10$,$g(k)=\sum\limits_{i=k}(-1)^{i-k}C_i^kf(i)$

$g(0)=\sum\limits_{i=0}^{n-1}(-1)^if(i)$

查看代码
 void Solve(int TIME) {

    int n, m;cin >> n >> m;
    if (n == 1) {
        if (m > 9) cout << 0 << endl;
        else cout << 1 << endl;
        return;
    }
    int res = 0;
    for (int x = 1;x <= 9;x++) {
        for (int i = 0;i <= n - 1;i++) {
            if (i & 1) res = (res + (MOD - 1) * comb(n - 1, i) % MOD * comb(m - x - 10 * i + n - 2, n - 2) % MOD) % MOD;
            else res = (res + comb(n - 1, i) * comb(m - x - 10 * i + n - 2, n - 2) % MOD) % MOD;
        }
    }
    cout << res << endl;

}

 

28.2023ICPC区域赛杭州站B

知识点

分块,FFT/NTT/MTT

题解

首先根据它的相对误差得知,如果在坐标$[x,3x]$有满足条件的坐标,那么$1.5x$可以保证正确。

因此只需要对每个$d$找到最小的$k$满足$[3^k,3^{k+1})$,一共是$log_3n$个区间。

令$A_i$表示坐标为$i$,编号在$[3^k,3^{k+1})$​中灯的颜色,如果不存在则为$0$。

令$A_i$表示坐标为$i$的灯的颜色,不存在则为$0$。

$\sum\limits_{i=1}^{n}[A_i>0][B_{i+d}>0](A_i-B_{i+d})^2$

$=\sum\limits_{i=1}^{n}[A^T_{n-i}>0][B_{i+d}>0](A^T_{n-i}-B_{i+d})^2$

得$res_{n+d}=\sum\limits_{i=1}^{n}[A^T_{n-i}>0][B_{i+d}>0][(A^T_{n-i})^2+(B_{i+d})^2-2A^T_{n-i}B_{i+d}]$

那么$d$有解,就有$res_{n+d}\ne 0$。

令$N=250000$, 预处理复杂度$O(log_3nNlogN)=O(NlogNlogn)$,总复杂度$O(NlogNlogn+q)$。

注意这里应该用大模数NTT/MTT或者FFT。

查看代码
 const int LEN = 250010;

void Solve(int TIME) {

    int n, q;cin >> n >> q;
    vc<ai2> a(n + 1);
    vc<ai2> pos(LEN + 1);
    for (int i = 1;i <= n;i++) {
        int x, c;cin >> x >> c;
        a[i] = { x,c };//下标i,坐标x,颜色c
        pos[x] = { i,c };
    }

    vi qry(q + 1);
    for (int i = 1;i <= q;i++) {
        int d;cin >> d;
        qry[i] = d;
    }
    vc<ld> res(q + 1);
    for (int l = 1;l <= n;l = l * 3) {
        int r = min(l * 3 - 1, n);
        Poly A(LEN + 1), B(LEN + 1);
        Poly As(LEN + 1), Bs(LEN + 1);
        Poly ea(LEN + 1), eb(LEN + 1);
        for (int i = 1;i <= LEN;i++) ea[i] = (l <= pos[i][0] && pos[i][0] <= r);
        for (int i = 1;i <= LEN;i++) A[i] = ea[i] ? pos[i][1] : 0, As[i] = A[i] * A[i];
        for (int i = 1;i <= LEN;i++) eb[i] = (pos[i][0] != 0);
        for (int i = 1;i <= LEN;i++) B[i] = pos[i][1], Bs[i] = B[i] * B[i];
        A.rev(), As.rev(), ea.rev();
        Poly res1 = As * eb;
        Poly res2 = Bs * ea;
        Poly res3 = A * B;
        for (int i = 1;i <= q;i++) {
            if (res[i]) continue;
            if (LEN + qry[i] > res1.deg()) continue;
            int x = res1[LEN + qry[i]] + res2[LEN + qry[i]] - 2 * res3[LEN + qry[i]];
            if (x) res[i] = 1.5 * l;
        }
    }
    for (int i = 1;i <= q;i += 1) cout << res[i] << endl;

}

 

29.CF1929F - Sasha and the Wedding Binary Search Tree

知识点

隔板法

题解

要求中序遍历后得到的序列不下降。

每两个相邻的非$-1$下标,不妨设为$L,R$。设它们的值为$a_L,a_R$。

只需要它们之间的数字$-1$都被替换成$[a_L,a_R]$之间的数字,并且不下降即可。求这样的序列数量,最后用乘法原理相乘即得出答案。

相当于给$len=R-L-1$个小球分到$x=a_r-a_l+1$个盒子中,盒子可以为空。这是经典的隔板法,答案即为$C_{len-1+x}^{x-1}=C_{len-1+x}^{len}$,由于题目中范围很大,但是最终的累加值也就是$\sum len\le5e5$,考虑预处理阶乘的逆元,然后用$\frac{A_n^m}{m!}$来计算。这是求组合数的一个经典技巧。

查看代码
 void Solve(int TIME) {

    int n, C;cin >> n >> C;
    vc<ai3> g(n + 1);
    for (int i = 1;i <= n;i++) {
        int l, r, x;cin >> l >> r >> x;
        g[i] = { l,r,x };
    }
    vi perm;
    auto dfs = [&](auto&& dfs, int u)->void {
        if (g[u][0] != -1) dfs(dfs, g[u][0]);
        perm.push_back(g[u][2]);
        if (g[u][1] != -1) dfs(dfs, g[u][1]);
        };
    dfs(dfs, 1);
    int m = perm.size();perm.insert(perm.begin(), 1);perm.push_back(C);
    int res = 1;
    for (int i = 0;i <= m;) {
        int st = i;i++;
        while (perm[i] == -1) i++;
        int l = perm[st], r = perm[i], x = r - l + 1;
        int len = i - st - 1;
        if (len == 0) continue;
        res = res * comb(len - 1 + x, len) % MOD;
    }

    cout << res << endl;

}

30.P4173 残缺的字符串

知识点

FFT/NTT

题解

首先介绍FFT如何求朴素的字符串匹配

首先把两个字符串转成多项式。$A(x)=a_0+a_1x^1+...+a_{n-1}x^{n-1}$,$B(x)=b_0+b_1x^1+...+b_{m-1}x^{m-1}$

定义一个多项式$C(x)$,其中它的第$j$项$c_jx^j$的系数$c_j=\sum\limits_{i=0}^{m-1}(a_{j+i}-b_i)^2$

当这个式子为$0$的时候,字符串$a$的子串$a_ja_{j+1}...a_{j+m-1}$与字符串$b$匹配成功。

然后我们考虑如何计算这个多项式$C(x)$,我们需要将其系数转换为卷积形式。

翻转字符串$b$,设$b^T=reverse(b)$,那么$b^T_i=b_{m-i-1}$。

于是$c_j=\sum\limits_{i=0}^{m-1}(a_{j+i}-b_i)^2=\sum\limits_{i=0}^{m-1}(a_{j+i}-b^T_{m-i-1})^2$

$=\sum\limits_{i=0}^{m-1}(a_{j+i})^2+\sum\limits_{i=0}^{m-1}(b^T_{m-i-1})^2-2\sum\limits_{i=0}^{m-1}a_{j+i}b^T_{m-i-1}$

前两项可以直接$O(n)$预处理,最后一项可以$FFT/NTT$加速$O(nlogn)$计算。

 

引入通配符后,再判断字符权值大于0即可。

$c_j=\sum\limits_{i=0}^{m-1}(a_{j+i}-b_i)^2a_{j+i}b_i=\sum\limits_{i=0}^{m-1}(a_{j+i}-b^T_{m-i-1})^2a_{j+i}b^T_{m-i-1}$

$=\sum\limits_{i=0}^{m-1}(a_{j+i}-b^T_{m-i-1})^2a_{j+i}b^T_{m-i-1}$

$=\sum\limits_{i=0}^{m-1}(a_{j+i})^3b^T_{m-i-1}+\sum\limits_{i=0}^{m-1}(b^T_{m-i-1})^3a_{j+i}-2\sum\limits_{i=0}^{m-1}(a_{j+i})^2(b^T_{m-i-1})^2$

当然也可以用28的写法,也是可以过的。

查看代码
 void Solve(int TIME) {

    int m, n;string b, a;cin >> m >> n >> b >> a;
    auto get = [&](char ch) {
        if (ch == '*') return 0;
        else return ch - 'a' + 1;
        };
    Poly A(n), A2(n), A3(n), B(m), B2(m), B3(m);
    for (int i = 0;i < n;i++) {
        A[i] = get(a[i]);
        A2[i] = A[i] * A[i];
        A3[i] = A[i] * A[i] * A[i];
    }
    for (int i = 0;i < m;i++) {
        B[i] = get(b[i]);
        B2[i] = B[i] * B[i];
        B3[i] = B[i] * B[i] * B[i];
    }
    B.rev(), B2.rev(), B3.rev();
    Poly res1, res2, res3;
    res1 = A3 * B;
    res2 = B3 * A;
    res3 = A2 * B2;
    vi res(n - m + 1);
    int cnt = 0;
    for (int i = 0;i <= n - m;i++) {
        res[i] = res1[i + m - 1] + res2[i + m - 1] - 2 * res3[i + m - 1];
        if (res[i] == 0) cnt++;
    }
    cout << cnt << endl;

    for (int i = 0;i <= n - m;i++) {
        if (res[i] == 0) cout << i + 1 << " ";
    }
    cout << endl;

}

31.CF528D Fuzzy Searc

知识点

FFT/NTT

题解

当字符集较小时,还有一种解法。(24.小纸条就是用该方法)

不妨假设字符集中仅含有字母$A,B,C$。

首先将字符串中的$A$置为$1$,$B,C$置为$0$。由于只有$01$状态,因此$c_j=\sum\limits_{i=0}^{m}a_{i+j}b_i$。

之后分别再将$B$置为$1$,其他两项置为$0$。以及$C$置为$1$,其他两项置为$0$。将它们的结果都累加,最后如果$c_j=m$则匹配成功。

时间复杂度$O(|S|nlogn)$,$|S|$表示字符集大小。这个方法可以比上面的方法延申出更多的用处。

 

这题,只需要给主串的待匹配字符往左右扩k个,然后用模式串去匹配即可。

查看代码
 void Solve(int TIME) {

    int n, m;cin >> n >> m;
    int k;cin >> k;
    string a, b;cin >> a >> b;
    vi res(n - m + 1);
    int lst = -inf;
    for (auto ch : { 'A','T','G','C' }) {
        Poly A(n), B(m);
        lst = -1e9;
        for (int i = 0;i < n;i++) {
            if (a[i] == ch) lst = i;
            if (i - lst <= k) A[i] = 1;
        }
        lst = 1e9;
        for (int i = n - 1;i >= 0;i--) {
            if (a[i] == ch) lst = i;
            if (lst - i <= k) A[i] = 1;
        }
        for (int i = 0;i < m;i++) {
            if (b[i] == ch) B[i] = 1;
        }
        B.rev();
        A = A * B;
        for (int i = 0;i < n - m + 1;i++) res[i] += A[m + i - 1];
    }
    int ans = 0;
    
    for (auto i : res) ans += i == m;

    cout << ans << endl;
}

32.2017-2018 ICPC, Asia Daejeon Regional Contest - H

知识点

FFT/NTT

题解

也是一样的,根据剪刀石头布的克制关系弄一下,然后FFT。

查看代码
 void Solve(int TIME) {

    int n, m;cin >> n >> m;
    string a, b;cin >> a >> b;
    vi res(n);
    char ch[] = { 'P', 'S', 'R' };
    for (int i = 0;i < 3;i++) {
        int ch1 = ch[i], ch2 = ch[(i + 1) % 3];
        Poly A(n), B(m);
        for (int i = 0;i < n;i++) {
            A[i] = a[i] == ch1;
        }
        for (int i = 0;i < m;i++) {
            B[i] = b[i] == ch2;
        }
        B.rev();
        A = A * B;
        for (int i = 0;i < n;i++) res[i] += A[m + i - 1];
    }
    int ans = 0;

    for (auto i : res) ans = max(ans, i);

    cout << ans << endl;
}

 

33.CF827E. Rusty String

知识点

FFT/NTT

题解

正着不好统计,正难则反。考虑求出所有不符合要求的周期长度,即存在$S_i\ne S_{i+d}$。

因此构造多项式$C(x)$,系数$c_j=\sum\limits_{i=0}^{n-1}[s_i\ne s_{i+j}]$,字符集很小考虑用$01$多项式。$?$如果视作通配符,那么直接置$0$即可。于是$c_j=\sum\limits_{i=0}^{n-1}[s_i\neq s_{i+j}]=\sum\limits_{i=0}^{n-1}s_is'_{i+j}=\sum\limits_{i=0}^{n-1}s^T_{n-1-i}s'_{i+j}$。其中$s'$和$s$置$1$时的字符不同。

然而发现有问题,同一个位置$?$不能同时为$V$和$K$。

举例:

 V  ?  ?  V K

​         V ?  ? V K

注意到第2个$?$需要同时是$V$和$K$。即出现了$s_0=s_2=s_4$。

为什么会出现这种情况呢?因为我们只判断了$s_i$和$s_{i+d}$的关系,但这个关系没有传递性。就像上面出现的$V=?=K$的情况,这就错了。

不难注意到,对于$k>1$,如果$s_i$和$s_{i+kd}$中,至少存在一个不能匹配,那么可以确定这是一个假的周期,因为真周期的$k$倍也必然是一个周期。用卷积求出之后,埃筛$O(nlogn)$判断一下就可以了。

查看代码
 void Solve(int TIME) {

    int n;cin >> n;
    string s;cin >> s;
    vi res(n);
    char ch[] = { 'V','K' };
    for (int i = 0;i < 2;i++) {
        Poly S(n), RS(n);
        for (int j = 0;j < n;j++) {
            if (s[j] == ch[i]) S[j] = 1;
            if (s[j] == ch[(i + 1) % 2]) RS[j] = 1;
        }
        RS.rev();
        S = S * RS;
        for (int j = 0;j < n;j++) res[j] += S[n + j - 1];
    }
    vi period(n + 1);period[n] = 1;
    for (int i = 1;i < n;i++)
        if (!res[i]) period[i] = 1;
    for (int i = n;i >= 1;i--) {
        if (period[i] == 0) continue;
        for (int j = i + i;j <= n;j += i) {
            if (period[j] == 0) {
                period[i] = 0;
                break;
            }
        }
    }
    cout << accumulate(period.begin(), period.end(), 0) << endl;
    for (int i = 1;i <= n;i++) {
        if (period[i]) cout << i << " ";
    }
    cout << endl;


}

 

34.A+B Problem

设$c_i$表示数字$i$在数组中的出现次数,那么对于询问$x$,答案为$\sum\limits_{i+j=x}min(c_i,c_j)$

考虑优化求和式子,用$cnt$枚举数字的出现次数来将式子中的$min$转换掉,这样对于每个$x$,所求即为$\sum\limits_{cnt=1}^{n}\sum\limits_{i+j=x}[c_i\ge cnt][c_j\ge cnt]$,令$b_i=[c_i\ge cnt]$,$b_j=[c_j\ge cnt]$。转化为$\sum\limits_{i+j=x}b_ib_j$,发现这是一个卷积的形式,使用$FFT/NTT$可以做到$O(mlogm)$求解。

再注意到$n=\sum\limits_{i=1}^{m}c_i$,有一个经典的性质,不同的$c_i$种数至多有$O(\sqrt n)$。用$\sum\limits_{i=1}^{\sqrt {2n}}=\frac1 2(1+\sqrt{2n})\sqrt {2n}>n$,可以简单证明。因此上述式子中$cnt$只有$O(\sqrt n)$种不同的取值,枚举不同的$c_i$即可,然后进行依次卷积。复杂度是$O(\sqrt nmlogm)$。大于$\sqrt n$的部分暴力枚举两个数,复杂度$O((\sqrt n)^2)=O(n)$。这个根号分治给了我们一定的启发。

考虑设置阈值$B$,当$cnt\le B$,使用上述式子$\sum\limits_{cnt=1}^{B}\sum\limits_{i+j=x}[c_i\ge cnt][c_j\ge cnt]$,时间复杂度$O(Bmlogm)$。

当$cnt> B$,满足这个条件的不同数字个数不超过$O(\frac{n}{B})$个,于是通过$O(\frac{n^2}{B^2})$暴力枚举两个数字$i,j$进行计算,即$\sum\limits_{i+j=x}min(c_i,c_j)-B$。减去$B$是为了去掉$cnt\le B$时数字$i,j$的贡献。

令$Bmlogm=\frac{n^2}{m^2}$,得到当$B=(\frac{n^2}{mlogm})^{\frac1 3}$,时间复杂度达到最优。于是总时间复杂度为$O((nmlogm)^\frac{2}3)$。由于$FFT$的常数较大,可以适当降低$B$,可能会使得程序运行更快。

最后的答案要除以$2$。这样统计方便,不需要讨论两个数字相等的情况。

PS:实测$B=(\frac{n^2}{mlogm})^{0.3}$跑的比较快

查看代码
 void Solve(int TIME) {

    int n, m, q;cin >> n >> m >> q;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    vi c(m + 1);for (int i = 1;i <= n;i++) c[a[i]] += 1;
    int B = pow(1.0 * n * n / (m * log2(m)), 0.3);
    vi res(2 * m + 1);
    {//<=B
        for (int i = 1;i <= B;i++) {
            Poly F(m + 1);
            for (int j = 0;j <= m;j++) F[j] = (c[j] >= i);
            F = F * F;
            for (int j = 0;j <= m + m;j++) res[j] += F[j];
        }
    }

    {//>B
        vi v;
        for (int i = 1;i <= m;i++) {
            if (c[i] > B) v.push_back(i);
        }
        for (auto i : v) {
            for (auto j : v) {
                res[i + j] += min(c[i], c[j]) - B;
            }
        }
    }
    for (int i = 0;i <= m + m;i++) res[i] /= 2;
    while (q--) {
        int x;cin >> x;
        cout << res[x] << endl;
    }

}

 

35.P4721 分治 FFT

知识点

FFT/NTT,多项式求逆元

题解

对于右区间的点$x\in[mid+1,r]$,贡献是$h_x=\sum\limits_{i=l}^{mid}f_i\times g_{x-i}$ 。然后就可以分治计算了,复杂度$O(nlog^2n)$。

查看代码
 void Solve(int TIME) {

    int n;cin >> n;
    vi g(n);for (int i = 1;i < n;i++) cin >> g[i];
    vi f(n);f[0] = 1;
    auto dfs = [&](auto&& dfs, int l, int r) {
        if (l == r) return;
        int mid = l + r >> 1;
        dfs(dfs, l, mid);
        Poly A(mid - l + 1);for (int i = 0;i <= mid - l;i++) A[i] = f[i + l];
        Poly B(r - l + 1);for (int i = 0;i <= r - l;i++) B[i] = g[i];
        A = A * B;
        for (int i = mid + 1;i <= r;i++) f[i] = (f[i] + A[i - l]) % MOD;
        dfs(dfs, mid + 1, r);
        };
    dfs(dfs, 0, n - 1);
    for (int i = 0;i < n;i++) cout << f[i] << " ";

}

也可以多项式逆元:$F(x)G(x)=\sum\limits_{i=0}x^i\sum\limits_{j+k=i}f_jg_k=F(x)-f_0x^0$,即$F(x)(1-G(x))=f_0x^0$,

得到$F(x)=\frac{1}{1-G(x)}$,多项式求逆元即可。复杂度$O(nlogn)$。

查看代码
 void Solve(int TIME) {

    int n;cin >> n;
    vi g(n);for (int i = 1;i < n;i++) cin >> g[i];
    Poly A(n);for (int i = 1;i < n;i++) A[i] = Dec(MOD, g[i]);A[0] = 1;
    A = Inv(A);
    for (int i = 0;i < n;i++) cout << A[i] << " ";

}

 

知识点

二项式反演

题解

二项式反演:

设$f(x)$表示钦定有$x$个2023,$f(x)=C_{n-3x}^{x}10^{n-4x}$ 。

其中$C_{n-3x}^{x}$可以用隔板法理解,即在$x$个2023形成的$x+1$个空中,放入$n-4x$个字符,即$a_1+a_2+...a_{x+1}=n-4x$的非负整数解个数。

$g(x)$表示刚好$x$个2023,$g(x)=\sum\limits_{i=x}^{n}(-1)^{i-x}C_{i}^{x}f(i)$

$res=g(m)=\sum\limits_{i=m}^{n}(-1)^{i-m}C_{i}^{m}f(i)=\sum\limits_{i=m}^{n}(-1)^{i-m}C_{i}^{m}C_{n-3i}^{i}10^{n-4i}$

时间复杂度$O(n)$。

查看代码
 void Solve() {
    int n, m;cin >> n >> m;
    int res = 0;
    for (int i = m;4 * i <= n;i++) {
        int t = comb(i, m) * comb(n - 3 * i, i) % MOD * pw[n - 4 * i] % MOD;
        if ((i - m) & 1) res = (res - t + MOD) % MOD;
        else res = (res + t) % MOD;
    }
    cout << res << endl;
}

 

37.2929. 给小朋友们分糖果 II - 力扣(LeetCode)

知识点

广义容斥/二项式反演

题解

见trick2中二项式反演部分,里面记录了我对二项式反演的新的理解。

$(1-a_1)(1-a_2)(1-a_3)=1-\sum\limits_{1\le i\le 3}a_i+\sum\limits_{1\le i< j\le 3}a_ia_j-a_1a_2a_3$,令$a_i$表示第$i$人是违法的,$(1-a_i)$表示合法,那么即求上面这个式子。这里的违法指的是,这个人超过了$limit$,其他人可能超过可能不超过。(即二项式反演中的”钦定“)。

查看代码
 class Solution {
public:
    long long distributeCandies(int n, int limit) {
        auto calc = [&](int x)->ll {
            if (x <= 0) return 0;
            return 1LL * x * (x - 1) / 2;
            };
        ll res = 0;
        res += 1LL * calc(n + 2);
        res -= 1LL * 3 * calc(n + 2 - (limit + 1));
        res += 1LL * 3 * calc(n + 2 - 2 * (limit + 1));
        res -= 1LL * calc(n + 2 - 3 * (limit + 1));
        return res;
    }
};

 

 

38. E. Devu and Flowers (codeforces.com)

知识点

二项式反演/广义容斥 

题解

同上。注意这里的限制各不相同,所以需要状压一一枚举。

查看代码
 void Solve() {
    int n, s;cin >> n >> s;
    vector<int> a(n);for (int i = 0;i < n;i++) cin >> a[i];
    int u = (1 << n) - 1;
    int res = 0;
    for (int mask = 0;mask <= u;mask++) {
        int up = s + n - 1, dw = n - 1;
        for (int i = 0;i < n;i++) {
            if (mask >> i & 1) {
                up -= a[i] + 1;
            }
        }
        if (__builtin_popcount(mask) & 1) {
            res = (res - comb(up, dw) + MOD) % MOD;
        }
        else {
            res = (res + comb(up, dw)) % MOD;
        }
    }
    cout << res << endl;
}

 

39. J-Zero_2024牛客暑期多校训练营4 (nowcoder.com)

知识点

NTT/FFT

题解

对于一段不包含$0$的区间,令$a_i$为前$i$个数字全为$1$的概率的前缀积。$a_i=\prod\limits_{i=1}^{n}([s_i=1]\cdot 1+[s_i=?]\cdot \frac12)$

以$i$为结尾,长度为$j$的区间全为$1$的概率是$\frac{a_i}{a_{i-j}}$.

令$b_i=\frac{1}{a_{n-i}}$,于是以$i$为结尾的长度为$j$的区间是全$1$的概率是$a_i b_{n+j-i}$ 。

于是长度为$j$的区间期望数量就是$\sum\limits_{i,j}a_ib_{n+j-i}$。令$c_{j}$为多项式的$n+j$项。

那么答案即为$\sum\limits_{i=1}^{n}c_j\cdot j^k$

 

查看代码
 void Solve() {
    int n, k;cin >> n >> k;
    const int inv2 = (MOD + 1) >> 1;
    string s;cin >> s;s = ' ' + s;
    vector<int> res(n + 1);
    for (int i = 1;i <= n;) {
        if (s[i] == '0') { i++; continue; }
        int ks = i;
        i++;
        while (i <= n && s[i] != '0') i++;
        int l = ks, r = i - 1;
        int m = r - l + 1;
        Poly A(m + 1), B(m + 1);A[0] = 1;
        for (int j = l;j <= r;j++) A[j - l + 1] = A[j - l] * (s[j] == '?' ? inv2 : 1) % MOD;
        for (int j = 0;j <= m;j++) B[j] = qpow(A[m - j], MOD - 2);
        A = A * B;
        for (int j = m + 1;j <= m + m;j++) {
            res[j - m] = (res[j - m] + A[j]) % MOD;
        }
    }
    int Ans = 0;
    for (int i = 1;i <= n;i++) {
        Ans = (Ans + res[i] * qpow(i, k) % MOD) % MOD;
    }
    cout << Ans << endl;
}

 

A-活动_集美大学第十一届校程序设计竞赛(同步赛)

考虑将

将三个式子分别写成多项式。

$x^0+(C_2^2)x^2+\frac{C_4^2C_2^2}{2!}x^4+\frac{C_6^2C_4^2C_2^2}{3!}x^4+...+kx^{n-n\;mod\;2}$

然后考虑到需要先从n个里面选择这么指定的人才行,也就是$C_n^{x}C_{n-x}^{y}C_{n-x-y}^{z}=\frac{n!}{x!y!z!}$。也就是一个EGF。

其中上面的式子可以进行化简。剩下两个也是差不多。

查看代码
 void Solve() {
    const int M = 1e6;
    Poly A(M + 1), B(M + 1), C(M + 1);
    for (int i = 0;i <= M;i++) {
        if (i % 2 == 0) {
            A[i] = 1LL * qp(infact[2], i / 2) * infact[i / 2] % MOD;
        }
        if (i % 3 == 0) {
            B[i] = 1LL * qp(infact[3], i / 3) * infact[i / 3] % MOD;
        }
        if (i % 4 == 0) {
            C[i] = 1LL * qp(infact[4], i / 4) * infact[i / 4] % MOD;
        }
    }
    A = (A * B).extend(M + 1);
    A = (A * C).extend(M + 1);
    int t;cin >> t;
    while (t--) {
        int n;cin >> n;
        cout << 1LL * fact[n] * A[n] % MOD << endl;
    }
}

也可以dp做,设$f_i$表示前i个人的分组方案数。

 

 

C - Sum of Three Integers

首先用卷积算出两个数字$a_i+a_j,(i<j)$能表示的数量。然后枚举$a_k$,去掉$a_k$的贡献后看有没有$a_i+a_j=x-a_k$即可。由于种类可能比较大,需要使用MTT.

查看代码
 void Solve() {
    int n, x;cin >> n >> x;
    vector<int> a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    Poly A(1e6 + 1);
    vector<int> cnt(1e6 + 1);
    for (int i = 1;i <= n;i++) A[a[i]]++, cnt[a[i]]++;
    A = A * A;

    for (int i = 1;i <= n;i++) {
        A[a[i] + a[i]]--;
    }
    for (int i = 1;i <= 2e6;i++) {
        A[i] /= 2;
    }
    for (int i = 1;i <= n;i++) {
        int goa = x - a[i];
        if (goa < 0) continue;
        int c = ll(A[goa] + 0.5);
        if (goa - a[i] > 0) c -= cnt[goa - a[i]];
        if (a[i] + a[i] == goa) c++;
        if (c) {
            vector<int> pos(1e6 + 1);
            for (int j = 1;j <= n;j++) {
                if (j == i) continue;
                if (goa - a[j] > 0 && pos[goa - a[j]]) {
                    vector<int> v = { pos[goa - a[j]],i,j };
                    sort(v.begin(), v.end());
                    cout << v[0] << ' ' << v[1] << " " << v[2] << endl;
                    return;
                }
                if (!pos[a[j]]) pos[a[j]] = j;
            }
        }
    }
    cout << "-1\n";
}

 

 

3333. 找到初始输入字符串 II - 力扣(LeetCode)

长度为$len$的块贡献是 $x+x^2+...+x^{len}=x\frac{1-x^{len}}{1-x}$。

设总共有$n$块相同字符,所有的块相乘,即$\frac{x^n}{(1-x)^n} \prod \limits_{i=1}^{n}1-x^{len_i}$,去掉$x^n$之后,取$log$得到$\sum\limits_{i=1}^{n}\log(1-x^{len_i})-n\log(1-x)$。根据泰勒展开,调和级数枚举加上贡献,最后再$exp$即可。

查看代码
 class Solution {
public:
    int possibleStringCount(string s, int k) {
        int tot = 1;
        vector<int> cnt(k);
        Poly a(k);
        int n = 0;
        for (int i = 0;i < s.size();) {
            int ks = i;
            i++;
            while (i < s.size() && s[i] == s[i - 1]) i++;
            int len = i - ks;
            tot = 1LL * tot * len % MOD;
            n++;
            if (len < k) cnt[len]++;
        }
        k = k - 1 - n;
        if (k < 0) return tot;
        for (int i = k;i > 0;i--) {
            if (cnt[i] > 0) {
                int t = cnt[i];
                for (int j = 1;j * i <= k;j++) {
                    a[j * i] = (a[j * i] + MOD - 1LL * t * _inv[j] % MOD) % MOD;
                }
            }
        }

        for (int i = 1;i <= k;i++) {
            a[i] = (a[i] + 1LL * n * _inv[i] % MOD) % MOD;
        }

        a = Exp(a);

        for (int i = 0;i <= k;i++) {
            tot = (tot + MOD - a[i]) % MOD;
        }
        return tot;
    }
};

 

 

 

 

 

posted @ 2023-10-05 16:57  mrsunss  阅读(72)  评论(4)    收藏  举报