数学——多项式与计数
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}$种
例子:使用红蓝绿三种颜色,对一行$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
观察上面的例子,还能发现当集合无序且不是相互独立时,需要对集合卷积的结果再除以集合个数的阶乘
(集合之间相互独立,即指两个不同集合的元素不能调换。)
常用幂级数(生成函数封闭形式)
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$ 行
模板题:
$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方法:
将$n$划分成多个不大于$m$的数,不可重复,无序
考虑每个数不大于$m$的数,只有$1$个或$0$个的情况。生成函数$F(x)=\prod\limits_{i=1}^{m}(1+x^i)$
dp方法:
小球入盒
现在有$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$构造
$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;
}
}
查看代码
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 情侣?给我烧了
设$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 - 力
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;
}
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;
}
考虑将
将三个式子分别写成多项式。
$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个人的分组方案数。
首先用卷积算出两个数字$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;
}
};