算法模板

对拍

int Rand(int x) {
    return rand() % x;
}
\\ 对某个数组重新排序
srand(time(nullptr));
random_shuffle(a + 1, a + n + 1, Rand);

__int128的输入输出模板:

inline __int128 read()
{
    __int128 x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch>'9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch - '0');
        ch = getchar();
    }
    return x * f;
}
void print(__int128 x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}

基础算法

倍增

int get(int l, int r) {
    int d = r - l + 1;
    int c = upper_bound(one, one + max_v + 1, d) - one - 1;
    return max(dp[l][c], dp[r - one[c] + 1][c]);
//    return min(dp[l][c], dp[r - one[c] + 1][c]);
}
void init() {
    for (int i = 0; i <= max_v; i++) one[i] = 1 << i;
    for (int d = 0; d <= max_v; d++) {
        for (int i = 1; i <= n; i++) {
            if (d == 0) dp[i][0] = a[i];
            else {
                int c = min(n, i + one[d - 1]);
                dp[i][d] = max(dp[i][d - 1], dp[c][d - 1]);
//                dp[i][d] = min(dp[i][d - 1], dp[c][d - 1]);
            }
        }
    }
}

马拉车

void init() {
    s[0] = '$', s[n * 2 + 1] = '#', s[n * 2 + 2] = '^';
    for (int i = 1; i <= n; i++) {
        s[i * 2 - 1] = '#';
        s[i * 2] = t[i];
    }
}
// S为所求字符串
int Manacher() {
    int ans = 0;
    init();
    n = n * 2 + 2;
    int id, mx = 0;
    for (int i = 1; i <= n; i++) {
        if (i < mx) {
            p[i] = min(p[id * 2 - i], mx - i);
        } else p[i] = 1;
        while (s[i - p[i]] == s[i + p[i]]) p[i]++;
        if (mx < i + p[i]) {
            id = i;
            mx = i + p[i];
        }
        ans = max(ans, p[i]);
    }
    return ans - 1;
}

最小表示法

// s为所给的字符串的两倍,长度为2*n
int get_min(char s[]) {
    int i = 0, j = 1;
    while (i < n && j < n) {
        int k = 0;
        while (k < n && s[i + k] == s[j + k]) k++;
        if (k == n) break;
        if (s[i + k] > s[j + k]) i += k + 1;
        else j += k + 1;
        if (i == j) j++;
    }
    int k = min(i, j);
    s[k + n] = 0;
    return k;
}

高精度

class BigInt : public vector<int> {
public:
    BigInt(int x = 0, int _mod = 0) {
        push_back(x), mod = _mod;
        process_digit();
    }
    bool operator > (const BigInt &a) {
        if (size() != a.size()) return size() > a.size();
        for (int i = size() - 1; i >= 0; i--) {
            if (at(i) != a[i]) return at(i) > a[i];
        }
        return false;
    }
    BigInt operator +(const BigInt &b) {
        for (int i = 0; i < size() || i < b.size(); i++) {
            if (i == size()) push_back(0);
            if (i < b.size()) at(i) += b[i];
        }
        process_digit();
        return (*this);
    }
    BigInt operator -(const BigInt &b) {
        for (int i = 0; i < size() || i < b.size(); i++) {
            if (i == size()) push_back(0);
            if (i < b.size()) at(i) -= b[i];
        }
        process_digit();
        return (*this);
    }
    BigInt operator *=(int x) {
        for (int i = 0; i < size(); i++) at(i) *= x;
        process_digit();
        return *this;
    }
    BigInt operator / (int x) const {
        BigInt ret(*this);
        int y = 0;
        for (int i = size() - 1; i >= 0; i--) {
            y = y * 10 + at(i);
            ret[i] = y / x;
            y %= x;
        }
        ret.process_digit();
        ret.mod = y;
        return ret;
    }
    int get_mod() {
        return mod;
    }
private:
    int mod;
    void process_digit() {
        for (int i = 0; i < size(); i++) {
            if (at(i) < 10 && at(i) >= 0) continue;
            if (i + 1 == size()) push_back(0);
            at(i + 1) += at(i) / 10;
            at(i) %= 10;
            if (at(i) < 0) at(i) += 10, at(i + 1) -= 1;
        }
        while (size() > 1 && back() == 0) pop_back();
    }
};
ostream &operator <<(ostream &out, const BigInt &a) {
    for (int i = a.size() - 1; i >= 0; i--) out << a[i];
    return out;
}
istream &operator >>(istream &in, BigInt &b) {
    b.clear();
    char c;
    while (c = in.get(), isdigit(c)) {
        b.push_back(c - '0');
    }
    reverse(b.begin(), b.end());
    return in;
}

搜索

双向广搜

// 内部
int extend(queue<string> &q, map<string, int> &da, map<string, int> &db) {
    // 队列规则
    return max + 1;
}
// 外部
int bfs(string x, string y) {
    queue<string> qa, qb;
    map<string, int> da, db;
    da[x] = 0, qa.push(x);
    db[y] = 0, qb.push(y);
    while (qa.size() && qb.size()) {
        int t;
        if (qa.size() <= qb.size())t = extend(qa, da, db);
        else t = extend(qb, db, da);
        if (t <= max) return t;
    }
    // max为最大限制
    return max;
}

欧拉回路

// 此模板用于解决一笔画问题,并记录路径。
void dfs(int u) {
    for (int &i = h[u], t; ~i;) {
        int j = e[i];
        if (st[i]) {
            i = ne[i]; //引用改变h[u]的值
            continue;
        }
        st[i] = true;
        
        
        i = ne[i];// h[u]删去以遍历的边,需要放在dfs前
        dfs(j);
        ans[++cnt] = t;
    }
}

数据结构

splay

// 将x这个点往上翻转
void rotate(int x) {
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
// 将x翻转到k的下方
void splay(int x, int k) {
    while (tr[x].p != k) {
        int y = tr[x].p, z = tr[y].p;
        if (k != z) {
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y))rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
    if (!k)root = x;
}
// 按照权值大小插入某个值保证单调性
void insert(int v) {
    int u = root, p = 0;
    // p为当前节点,u为需要放到的位置节点
    while (u != 0)p = u, u = tr[u].s[v > tr[u].v];
    u = ++idx;
    if (p)tr[p].s[v > tr[p].v] = u;
    tr[u].init(v, p);
    splay(u, 0);
}

主席树

// 结构体
struct node{
    int l, r;
    // int cnt;
}tr[N * 4 + N * 17];
// 哨兵,最开始未修改的线段树
int build(int l, int r) {
    int p = ++idx;
    if (l == r) {
        return p;
    }
    int mid = l + r >> 1;
    tr[p].l = build(l, mid);
    tr[p].r = build(mid + 1, r);
    return p;
}
// 插入最新版本的线段树,是从q那个版本转移而来
int insert(int q, int l, int r, int x) {
    int p = ++idx;
    tr[p] = tr[q];
    if (l == r) {
        tr[p].cnt++;
        return p;
    }
    int mid = l + r >> 1;
    if (x <= mid) tr[p].l = insert(tr[q].l, l, mid, x);
    else tr[p].r = insert(tr[q].r, mid + 1, r, x);
    push_up(p);
    return p;
}
// 查询l ~ r之间构成的线段树
int query(int p, int q, int l, int r, int k) {
    if (l == r) return vec[l];
    int mid = l + r >> 1;
    // 线段树操作

    // int d = tr[tr[p].l].cnt - tr[tr[q].l].cnt;
    // if (d >= k) return query(tr[p].l, tr[q].l, l, mid, k);
    // return query(tr[p].r, tr[q].r, mid + 1, r, k - d);
}

树链剖分

定义:
重子节点:表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。轻子节点:表示剩余的所有子结点。从这个结点到重子节点的边为 重边。到其他轻子节点的边为 轻边。若干条首尾衔接的重边构成 重链。把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
通过dfs序将这棵树转化成 \(log(n)\) 条区间(dfs序优先遍历重子节点)。

// Fa为父亲节点,top为重链的顶端节点, top[root] = root
void dfs1(int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == Fa[u]) continue;
        depth[j] = depth[u] + 1, Fa[j] = u;
        dfs1(j);
        son[u] += son[j];
    }
    son[u]++;
}
void dfs2(int u) {
    id[u] = ++cnt, node[cnt] = u;
    int t = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == Fa[u]) continue;
        if (son[j] > son[t]) t = j;
    }
    if (t) top[t] = top[u], dfs2(t);
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == Fa[u] || j == t) continue;
        top[j] = j;
        dfs2(j);
    }
}
ll query(int u, int v) {
    if (top[u] == top[v]) {
        if (depth[u] > depth[v]) swap(u, v);
        return query(id[u], id[v]); // u ~ v
    }
    if (depth[top[u]] <= depth[top[v]]) swap(u, v);
    return query(id[top[u]], id[u]) + query(Fa[top[u]], v); // top[u] ~ u
}

动态树(lct)

struct node{
    int s[2], p, v;
    int sum, rev;
}tr[N];
int n, m;
int stk[N];

void pushrev(int x) {
    swap(tr[x].s[0], tr[x].s[1]);
    tr[x].rev ^= 1;
}
void pushup(int x) {
    // tr[x].sum = tr[tr[x].s[0]].sum ^ tr[tr[x].s[1]].sum ^ tr[x].v;
}
void pushdown(int x) {
    if (tr[x].rev) {
        pushrev(tr[x].s[0]), pushrev(tr[x].s[1]);
        tr[x].rev = 0;
    }
}
int isroot(int x) {
    int p = tr[x].p;
    return tr[p].s[0] != x && tr[p].s[1] != x;
}
void rotate(int x) {
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y)) tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
void splay(int x) {
    int top = 0, r = x;
    stk[++top] = r;
    while (!isroot(r)) stk[++top] = r = tr[r].p;
    while (top) pushdown(stk[top--]);
    while (!isroot(x)) {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y)) {
            if ((tr[z].s[1] == y) ^ (tr[y].s[1] == x)) rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
}
void access(int x) { // 建立一条从根节点到x的路径,同时将x变为splay的根节点
    int z = x;
    for (int y = 0; x; y = x, x = tr[x].p) {
        splay(x);
        tr[x].s[1] = y;
        pushup(x);
    }
    splay(z);
}
void make_root(int x) { // 将x变为原树的根节点
    access(x);
    pushrev(x);
}
int find_root(int x) { // 找到x在原树中的根节点,再将原树中的根节点旋转到splay的根节点。
    access(x);
    while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];
    splay(x);
    return x;
}
void split(int x, int y) { // 给x和y之间的路径建立一个splay,且根节点是y
    make_root(x);
    access(y);
}
void link(int x, int y) { // 如果x和y不连通,则假如一条x和y之间的边
    make_root(x);
    if (find_root(y) != x) tr[x].p = y;
}
void cut(int x, int y) { // 如果x和y之间存在边,则删掉该边
    make_root(x);
    if (find_root(y) == x && tr[y].p == x && tr[y].s[0] == 0) {
        tr[x].s[1] = tr[y].p = 0;
        pushup(x);
    }
}

树上启发式合并(dsu)

// id为dfs序, node为dfs序上的节点
// big为重节点,son为子树节点个数
void dfs(int u, int fa) {
    id[u] = ++id_cnt, node[id_cnt] = u;
    son[u] = 1;
    for (auto j : g[u]) {
        if (j == fa) continue;
        dfs(j, u);
        son[u] += son[j];
        if (son[big[u]] < son[j]) big[u] = j;
    }
}
// keep为true表示需要保留答案
void dfs(int u, int fa, bool keep) {
    for (auto j : g[u]) {
        if (j == fa || j == big[u]) continue;
        dfs(j, u, false);
    }
    if (big[u]) dfs(big[u], u, true);
    for (auto j : g[u]) {
        if (j == fa || j == big[u]) continue;
        for (int i = id[j]; i < id[j] + son[j]; i++) add(node[i]);
    }
    add(u);
    // ans[u] = cnt[max_cnt];
    if (!keep) {
        for (int i = id[u]; i < id[u] + son[u]; i++) del(node[i]);
    }
}

字符串

KMP

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+5,M=1e6+5;
char p[N], s[M];
int ne[N];
int n, m;
int main() {
    cin >> n >> p + 1 >> m >> s + 1;
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1])j = ne[j];
        if (p[j + 1] == p[i])j++;
        ne[i] = j;
        while (ne[i] && p[i + 1] == p[ne[i] + 1]) ne[i] = ne[j];
    }
    for (int i = 1, j = 0; i <= m; i++) {
        while (j && s[i] != p[j + 1])j = ne[j];
        if (s[i] == p[j + 1])j++;
        if (j == n) {
            printf("%d ", i - n);
            j = ne[j];
        }
    }
    return 0;
}

字符串哈希

#include "iostream"
#include "cstdio"
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5 + 5, P = 131;
char str[N];
ULL h[N], p[N];
// 获取字符串哈希
ULL get(int l,int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}
int main() {
    // 初始化
    int n;
    scanf("%d", &n);
    scanf("%s", str);
    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i - 1];
    }
    return 0;
}

AC自动机

// 在字典树p这个节点处,对应的最长后缀的位置为ne[p]

// insert建立字典树
void insert() {
    int m = strlen(s + 1), p = 0;
    for (int i = 1; i <= m; i++) {
        int t = s[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
    }
    cnt[p]++;
}
// 通过字典树更新ne数组
void build() {
    queue<int> que;
    int p = 0;
    for (int i = 0; i < 26; i++) {
        if (tr[p][i]) que.push(tr[p][i]);
    }
    while (que.size()) {
        int u = que.front();
        que.pop();
        for (int i = 0; i < 26; i++) {
            int c = tr[u][i];
            if (!c) tr[u][i] = tr[ne[u]][i];
            else {
                ne[c] = tr[ne[u]][i];
                que.push(c);
            }
        }
    }
}

数学

欧拉函数

\(\varphi(m)=m\prod_{d|m}(1-\frac{1}{p})\)

欧拉定理

\(若正整数m,a,满足(a,m)=1,则\alpha^{\varphi(m)}\equiv 1(mod\ m)\)

欧拉降幂

\( A^K(mod\ m)= \begin{cases} A^{K\%\phi(m)},&m,A互质\\ A^{K\%\phi(m)+\phi(m)},&K\geqslant\phi(m)\\ A^{K} &K<\phi(m)\\ \end{cases} \)

数列求和公式

  • 平方和求和:\(\sum_{k=1}^{n}k^2=\frac{n(n+1)(n+2)}{6}\)

组合数公式

  • 杨辉恒等式: \(C(n,k)=C(n−1,k)+C(n−1,k−1)\)
  • 对称性:\(C(n,k)=C(n,n−k)\)
  • 单行和:\(\sum_{i=0}^{n}C(n,i)=2^n\)
  • 单行平方和:\(\sum_{i=0}^{n}C(n,i)^2=C(2n,n)\)
  • 斜60行和=反斜下一行对应值:\(\sum_{i=0}^{n}C(k+i,k)=C(k+n+1,k+1)\)
  • 30∘ 斜行和等于Fibonacci数列:

\[f[n] = \begin{cases} \sum_{i=0}^{n/2-1}C(n/2+i,2i+1),&n≡0mod2\\ \sum_{i=0}^{(n-1)/2}C((n-1)/2+i,2i),&n≡1mod2\\ \end{cases} \]

  • 递推式:\(C(n,i)=\frac{(n+1-i)}i*C(n,i-1)\)
  • 求组合数某一段的和:\(\sum_{i=a}^{b}C(i,j)=C(b+1,j+1)-C(a,j+1)\)
  • \(C(n,m)\)的奇偶性:n&m=m为奇,否则为偶(lucas定理推论)
  • 卡特兰数:
    • \(\frac{C(2n,n)}{n+1}\)
    • \(C(n+m,n)-C(n+m,n-1)\)
    • \(\frac{C(n+m,n)*(m+1-n)}{m+1}\)

整除分块:\(n/(n/x)\)

// 两个数的整除分块
for (int l = 1, r; l <= R; l = r + 1) {
    r = L / l ? min(L / (L / l), R / (R / l)) : R / (R / l);
}
// 一个数的整除分块
for (int l = 1, r; l <= n; l = r + 1) {
    r = n / (n / l);
}

扩展欧几里得

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    int t = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return t;
}

扩展欧几里得求逆元:

int get_inv(int _inv, int p) {
    int x, y;
    int t = exgcd(_inv, p, x, y);
    if (t != 1) return -1;
    return (x % p + p) % p;
}

中国剩余定理(CRT)

\( \begin{cases} x \equiv a_1\ (mod\ m_1)\\ x \equiv a_2\ (mod\ m_2)\\ ...\\ x \equiv a_n\ (mod\ m_n)\\ \end{cases} \)
\(N=\prod_{i=1}^{n}m_i\)
\(x \equiv\sum_{i=1}^{n}a_i*\frac{N}{m_i}*[(\frac{N}{m_i})^{-1}]_{m_i}(mod\ N)\)

乘法逆元

  • \(P=i*\lfloor{\frac{P}{i}}\rfloor+P\%i,P为模数\)
for(int i = 2; i <= n; i++) {
    inv[i] = (-inv[p % i] * (p / i)) % p + p;
}

容斥原理

积性函数

积性函数:对于任意互质的正整数a和b有性质\(f(ab)=f(a)f(b)\)的数论函数。
完全积性函数:对于任意正整数a和b有性质\(f(ab)=f(a)f(b)\)的数论函数。

常见的积性函数

  • \(1(n)-不变的函数,定义为1(n)=1(完全积性)\)
  • \(id(n) -单位函数,定义为id(n)=n(完全积性)\)
  • \(ε(n)-定义为:若n=1,ε(n)=1;若n>1,ε(n)=0。别称为“对于狄利克雷卷积的乘法单位”(完全积性)\)
  • \(σ_k(n)-n的所有正因子的k次幂之和\)
  • \(σ(n)-n的所有质因子之和(σ_1(n))\)
  • \(d(n)-n的正因子个数(σ_0(n))\)

常见的转换

  • \(id(n)=\sum_{d|n}\varphi(d)\)
  • \(ε(n)=\sum_{d|n}μ(d)\)
  • \(d(nm)=\sum_{i|n}\sum_{j|m}[gcd(i,j)=1]\)

狄利克雷卷积

\(h=f*g=\sum_{d|n}f(d)*g(\frac{n}{d})\)

莫比乌斯反演

  • 反演一:\(f(n)=\sum_{d|n} g(d), g(n)=\sum_{d|n}μ(\frac{n}{d})*f(d)\)
  • 反演二:\(f(d)=\sum_{d|n} g(n), g(d)=\sum_{d|n}μ(\frac{n}{d})*f(n)\)
void init(int n) { // 欧拉筛模板
    mobius[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) p[idx++] = i, mobius[i] = -1;
        for (int j = 0; p[j] <= n / i; j++) {
            st[i * p[j]] = true;
            if (i % p[j] == 0) break;
            mobius[i * p[j]] = mobius[i] * -1;
        }
    }
}
  • \(M[n]=\sum_{i=1}^nμ[i]\)
  • \(M[n]=1-\sum_{i=2}^nM[\lfloor{\frac{n}{i}}\rfloor]\)
ll Djs(int n) { // 杜教筛模板
    if (n < N) return mobius[n];
    if (mp.count(n)) return mp[n];
    int ans = 1;
    for (int l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        ans = (ans - 1ll * (r - l + 1) * Djs(n / l) % mod + mod) % mod;
    }
    return mp[n] = ans;
}

矩阵

struct Matrix{ // 矩阵模板
    int a[M][M];
    Matrix(int x = 0) {
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < M; j++) {
                if (i == j) a[i][j] = x;
                else a[i][j] = 0;
            }
        }
    }
    Matrix operator * (const Matrix &that) const {
        Matrix ret;
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < M; j++) {
                for (int k = 0; k < M; k++) {
                    ret.a[i][j] = limit(ret.a[i][j] + (ll)this->a[i][k] * that.a[k][j]);
                }
            }
        }
        return ret;
    }
    Matrix operator + (const Matrix &that) const {
        Matrix ret;
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < M; j++) {
                ret.a[i][j] = limit((ll)this->a[i][j] + that.a[i][j]);
            }
        }
        return ret;
    }
    static ll limit(ll x) {
        if (x >= mod) return x % mod;
        return x;
    }
};
Matrix qsm(Matrix &E, ll b) {
    Matrix ret = 1;
    while (b) {
        if (b & 1) ret = ret * E;
        E = E * E;
        b >>= 1;
    }
    return ret;
}

生成函数

形式幂级数常见的逆:

  • 常生成函数:\(A(x)B(x)=1\)
    • \(A(x)=\sum_{i=0}x^i\)\(B(x)=1-x\)
    • \(A(x)=\sum_{i=0}(ax)^i\)\(B(x)=1-ax\)
    • \(A(x)=\sum_{n=0}C^{k-1}_{n+k-1}x^n\)\(B(x)=(1-x)^k\)
    • \(A(x)=\sum_{i=0}f(x)^i\)\(B(x)=1-f(x)\)
  • 指数生成函数:
    • \(exp(x)=\sum_{n>=0}\frac{x^n}{n!}\)
    • \(exp(ax)=\sum_{n>=0}{\frac{(ax)^n}{n!}}\)
    • \(\frac{exp(x)+exp(-x)}{2}=\sum_{2|i}\frac{x^i}{i!}\)
  • 对数生成函数:
    • \(ln(1+x)=\sum_{n\geq 1}\frac{(-1)^{n+1}}{n}x^n\)
    • \(-ln(1-x)=\sum_{n\geq 1}\frac{1}{n}x^n\)

欧拉定理

\(a+bi=re^{i\theta},r=\sqrt{a^2+b^2},tan(\theta)=\frac{b}{a}\)

单位根:

\(\omega^n=1,\omega^k=e^{i\frac{2k\pi}{n}}\)
常见性质:

  • \(\omega_{n}^k=\omega_{2n}^{2k}\)
  • \(\omega_{2n}^{k+n}=-\omega_{2n}^{k}\)

DFT & IDFT

\[A(\omega_n^k)=B(\omega_{n/2}^{k})+\omega_n^kC(\omega_{n/2}^k)\\ A(\omega_n^{k+n/2})=B(\omega_{n/2}^{k})-\omega_n^kC(\omega_{n/2}^k) \]

  • \(\Omega_{ij}=\omega^{ij}_{n},n*\Omega^{-1}_{ij}=\omega^{-ij}_{n}\)
  • 系数矩阵:\(A=(a_0,a_1,...,a_n)\)
  • 点值矩阵:\(B=(b_0,b_1,...,b_n)\)
  • \(\Omega{A}=B,A=\Omega^{-1}B\)
struct Complex {
    double x, y;
    Complex(double _x = 0.0, double _y = 0.0) {
        x = _x, y = _y;
    }
    Complex operator-(const Complex &b) const {
        return Complex(x - b.x, y - b.y);
    }
    Complex operator+(const Complex &b) const {
        return Complex(x + b.x, y + b.y);
    }
    Complex operator*(const Complex &b) const {
        return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
    }
};
void change(Complex y[], int len) {
    int k;
    for (int i = 1, j = len / 2; i < len - 1; i++) {
        if (i < j) std::swap(y[i], y[j]);
        k = len / 2;
        while (j >= k) {
            j = j - k;
            k = k / 2;
        }
        if (j < k) j += k;
    }
}
// on == 1 时是 DFT,on == -1 时是 IDFT
void fft(Complex y[], int len, int on) {
    change(y, len);
    for (int h = 2; h <= len; h <<= 1) {
        Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
        for (int j = 0; j < len; j += h) {
            Complex w(1, 0);
            for (int k = j; k < j + h / 2; k++) {
                Complex u = y[k];
                Complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if (on == -1) {
        for (int i = 0; i < len; i++) {
            y[i].x /= len;
        }
    }
}

拉格朗日插值法

  • \(f(x)=\sum_{i=1}^ny_if_i(x),f_i(x)=\prod_{i\neq j}^n\frac{x-x_j}{x_i-x_j}\)
void init(int n){
    fact[0] = inv[0] = 1;
    for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % mod;
    inv[n] = qsm(fact[n], mod - 2);
    for (int i = n - 1; i >= 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
ll lagrange(ll x, int n) {
    if (x <= n) return y[x];
    ll ans = 0, t = 1;
    s1[0] = 1;
    for (int i = 1; i <= n; i++) {
        s1[i] = s1[i - 1] * (x - (i - 1)) % mod;
    }
    for (int i = n; i >= 0; i--) {
        s1[i] = s1[i] * t % mod;
        t = t * (x - i) % mod;
        ll s = inv[i] % mod;
        if ((n - i) & 1) s = s * (mod - inv[n - i]) % mod;
        else s = s * inv[n - i] % mod;
        ans += y[i] * s % mod * s1[i] % mod, ans %= mod;
    }
    return (ans + mod) % mod;
}

阶和原根

若正整数\(m,a\),满足\((a,m)=1\)

  • \(\alpha^{n}\equiv 1(mod\ m),最小的正整数n称为a模m的阶,记作\delta_m(a)\)

\(当a作为原根时,a\in[1,m-1]\)

  • \(若\delta_m(a)=\varphi(m),则称a为m的一个原根\)
  • \(只有模2,4,p^x,2p^x存在原根(P为奇质数)\)
  • \(当m>1,a是m的原根当且仅当对于任意\varphi(m)的质因子q_i,g^{\frac{\varphi(m)}{q_i}}\not\equiv 1(mod\ m)\)
  • \(若G为n的原根,则当gcd(i,\varphi(n))=1,G^i也为n的原根\)
// 判断原根是否存在
bool check(int n) {
    if (n == 2 || n == 4) return true;
    if (n % 4 == 0) return false;
    if (n % 2 == 0) n /= 2;
    for (int i = 2; i <= n / i; i++) {
        if (n % i == 0) {
            while (n % i == 0) {
                n /= i;
            }
            if (n == 1) return true;
            else return false;
        }
    }
    return true;
}
// 获取最小的原根
int find_rt(int p, int &phi) {
    phi = get_phi(p);
    if (!check(p)) return -1;
    if (p == 2) return 1;
    get_prime(phi);
    for (int g = 1; g < p; g++) {
        if (gcd(g, p) != 1) continue;
        bool flag = true;
        for (auto c : prime) {
            if (qsm(g, phi / c, p) == 1) {
                flag = false;
                break;
            }
        }
        if (flag) return g;
    }
    return -1;
}

指标(离散对数)

对于质数P,假设g是p的一个原根,满足\(g^c\equiv x(mod\ p)\),则称x的指标为c,记作\(ind(x)=c,当\forall 1\leq x,y<p有以下性质:\)

  • \(x\equiv y(mod\ p) \Leftrightarrow ind(x)\equiv ind(y)(mod\ \varphi(p))\)
  • \(ind(xy)=ind(x)+ind(y)\ (mod\ p)\)
  • \(ind(x^c)\equiv c\ ind(x)\ (mod\ p)\)

BSGS

\(求最小的正整数x使得a^x\equiv b(mod\ p)\)

int BSGS(int a, int b, int p) {
    if (1 % p == b % p) return 0;
    int k = (int)sqrt(p) + 1;
    unordered_map<int, int> mp;
    for (int i = 0, j = b; i < k; i++) {
        mp[j] = i;
        j = (ll)j * a % p;
    }
    int ak = 1;
    for (int i = 0; i < k; i++) ak = (ll)ak * a % p;
    for (int i = 1, j = ak; i <= k; i++) {
        if (mp.count(j)) {
            return (ll)k * i - mp[j];
        }
        j = (ll)j * ak % p;
    }
    return -INF;
}
int EXBSGS(int a, int b, int p) {
    b = (b % p + p) % p;
    if (1 % p == b) return 0;
    int x, y;
    int d = exgcd(a, p, x, y);
    if (d > 1) {
        if (b % d) return - INF;
        exgcd(a / d, p / d, x, y);
        return EXBSGS(a, (ll)b / d * x % (p / d), p / d) + 1;
    }
    return BSGS(a, b, p);
}

NTT

\(假设质数p满足p=r2^l+1,g为p的原根\)

  • \(可用g_n=g^{\frac{p-1}{n}}代替\omega_n\)
  • \(g_{2n}^{2k}\equiv g_n^k(mod\ p),(2n\leq 2^l)\)
  • \(g_{2n}^{n}\equiv -1(mod\ p),(2n\leq 2^l)\)
  • 做NTT时假如开的长度不够时,应该为循环卷积
    \( \sum_{k=0}^{n-1}g_n^{ik}g_{n}^{-kj}\equiv \begin{cases} n,&i = j\\ 0,&otherwise\\ \end{cases} (mod\ p)其中0\leqslant i,j<n \)

常见模数:

  • \(65537=2^{16}+1,g=3\)
  • \(998244353=119·2^{23}+1,g=3\)
  • \(1004535809=479·2^{21}+1,g=3\)
  • \(4179340454199820289=29·2^{57}+1,g=3\)

NTT公式:

\[A(g_n^k)=B(g_{n/2}^{k})+g_n^kC(g_{n/2}^k)\\ A(g_n^{k+n/2})=B(g_{n/2}^{k})-g_n^kC(g_{n/2}^k) \]

void change(ll y[], int len) {
    int k;
    for (int i = 1, j = len / 2; i < len - 1; i++) {
        if (i < j) std::swap(y[i], y[j]);
        k = len / 2;
        while (j >= k) {
            j = j - k;
            k = k / 2;
        }
        if (j < k) j += k;
    }
}
// on == 1 时是 DFT,on == -1 时是 IDFT
void NTT(ll y[], int len, int on) {
    change(y, len);
    for (int h = 2; h <= len; h <<= 1) {
        ll wn = qsm(3, (mod - 1) / h);
        if (on == -1) wn = qsm(wn, mod - 2);
        for (int j = 0; j < len; j += h) {
            ll w = 1;
            for (int k = j; k < j + h / 2; k++) {
                ll u = y[k];
                ll t = w * y[k + h / 2] % mod;
                y[k] = (u + t) % mod;
                y[k + h / 2] = (u - t % mod + mod) % mod;
                w = w * wn % mod;
            }
        }
    }
    if (on == -1) {
        ll inv_n = qsm(len, mod - 2);
        for (int i = 0; i < len; i++) {
            y[i] = y[i] * inv_n % mod;
        }
    }
}

分治NTT

int find(ll x) {
    return one[upper_bound(one, one + 19, x) - one];
}
void dfs(ll *f, int l, int r, int d) {
    if (l == r) {
        f[0] = Q[l].b;
        for (int i = 1; i <= Q[l].a; i++) {
            f[i] = C(Q[l].a, i);
        }
        return ;
    }
    int mid = l + r >> 1;
    ll *g = a[d], *h = b[d];
    int n = find(sum[r] - sum[l - 1]);
    for (int i = 0; i < n; i++) {
        g[i] = h[i] = 0;
    }
    dfs(g, l, mid, d + 1), dfs(h, mid + 1, r, d + 1);
    NTT(g, n, 1), NTT(h, n, 1);
    for (int i = 0; i < n; i++) {
        f[i] = g[i] * h[i] % mod;
    }
    NTT(f, n, -1);
}

牛顿迭代

\(给定多项式g(x),求满足g(f(x))=0的形式幂级数f(x)。\)

  • \(n=1时,解g(a_0)=0\)
  • \(假设已经求出来了前n项f(x)\equiv f_0(x)=a_0+a_1x+...+a_{n-1}x^{n-1}(mod\ x^n)\)
  • \(则f(x)\equiv f_0(x)-\frac{g(f_0(x))}{g'(f_0(x))}(mod\ x^{2n})\)

求逆:\(h(x)是给定的形式幂级数,求它的逆f(x),则g(f(x))=\frac{1}{f(x)}-h(x)=0,得到的迭代方程为\)

\[f(x)\equiv 2f_0(x)-f_0^2(x)h(x)\ (mod\ x^{2n}) \]

void polyinv(ll f[], const ll h[], int n) {
    static ll d[N];
    f[0] = qsm(h[0], mod - 2), f[1] = 0;
    for (int w = 2; w / 2 < n; w *= 2) {
        memcpy(d, h, w * 8);
        for (int i = w; i < 2 * w; i++) d[i] = f[i] = 0;
        NTT(f, 2 * w, 1), NTT(d, 2 * w, 1);
        for (int i = 0; i < 2 * w; i++) {
            f[i] = f[i] * (2ll - f[i] * d[i] % mod + mod) % mod;
            f[i] = (f[i] % mod + mod) % mod;
        }
        NTT(f, 2 * w, -1);
        for (int i = w; i < 2 * w; i++) f[i] = 0;
    }
}

开方:\(h(x)是给定的形式幂级数,求它的开方f(x),则g(f(x))=f(x)^2-h(x)=0,得到的迭代方程为\)

\[f(x)\equiv f_0(x)-\frac{f_0^2(x)-h(x)}{2f_0(x)}\ (mod\ x^{2n}) \]

void polysqrt(ll f[], const ll h[], int n) {
    static ll t[N], inv[N];
    f[0] = 1;
    inv[0] = inv[1] = f[1] = 0;
    ll inv2 = qsm(2, mod - 2);
    for (int w = 2; w / 2 < n; w *= 2) {
        memcpy(t, h, w * 8);
        polyinv(inv, f, w);
        for (int i = w; i < 2 * w; i++) inv[i] = t[i] = f[i] = 0;
        NTT(f, 2 * w, 1), NTT(t, 2 * w, 1);
        NTT(inv, 2 * w, 1);
        for (int i = 0; i < 2 * w; i++) {
            f[i] = (f[i] + t[i] * inv[i] % mod) * inv2 % mod;
        }
        NTT(f, 2 * w, -1);
        for (int i = w; i < 2 * w; i++) f[i] = 0;
    }
}

形式幂级数的更多运算

\(假设f(x)=a_1x+...+a_nx+...,g(x)=b_0+b_1x+...b_nx+...,则g复合f定义为c_0+c_1x+...+c_nx+...,满足c_0=b_0,c_n=\sum_{k=1}^{n}b_k\sum_{i_1+i_2+...+i_k=n}a_{i_1}a_{i_2}...a_{i_k},记作g○f或者g(f(x))\)

  • \(g(f(x))'=g'(f(x))f'(x)\)
  • \(f(x)满足[x^0]f(x)=0,由此可以定义exp(f(x))和ln(1+f(x))\)

计算:\(ln(f(x))需要满足[x^0]f(x)=1\)

\[(ln(f(x)))'=f'(x)\frac{1}{f(x)} \]

void polyln(ll f[], int n) {
    static ll f_inv[N], g[N];
    int lim = 1;
    while (lim < n) lim <<= 1;
    memcpy(g, f, lim * 8);
    for (int i = 0; i < n; i++) f[i] = f[i + 1] * (i + 1) % mod;
    polyinv(f_inv, g, n);
    for (int i = lim; i < 2 * lim; i++) f[i] = f_inv[i] = 0;
    NTT(f, 2 * lim, 1), NTT(f_inv, 2 * lim, 1);
    for (int i = 0; i < 2 * lim; i++) f[i] = f[i] * f_inv[i] % mod;
    NTT(f, 2 * lim, -1);
    for (int i = n - 1; i >= 1; i--) f[i] = f[i - 1] * qsm(i, mod - 2) % mod;
    f[0] = 0;
}

计算:\(exp(f(x))需要满足[x^0]f(x)=0\)

\(假设g(x)=exp(f(x)),则ln(g(x))-f(x)=0,构造h(x, f)=ln(x)-f,牛顿迭代,假设得到了模x^n下的答案g_0(x)\)

\[g(x)\equiv g_0(x)(1-ln(g_0(x))+f(x))\ (mod\ x^{2n}) \]

void polyexp(ll f[], int n) {
    static ll h[N], h_ln[N];
    memcpy(h, f, n * 8);
    memset(f, 0, n * 8);
    f[0] = 1;
    for (int w = 2; w / 2 < n; w *= 2) {
        memcpy(h_ln, f, w * 8);
        polyln(h_ln, w);
        for (int i = 0; i < w; i++) h_ln[i] = (-h_ln[i] + h[i] + mod) % mod;
        h_ln[0] = (h_ln[0] + 1) % mod;
        for (int i = w; i < 2 * w; i++) h_ln[i] = f[i] = 0;
        NTT(f, 2 * w, 1), NTT(h_ln, 2 * w, 1);
        for (int i = 0; i < 2 * w; i++) {
            f[i] = f[i] * h_ln[i] % mod;
        }
        NTT(f, 2 * w, -1);
        for (int i = w; i < 2 * w; i++) f[i] = 0;
    }
}

计算:\(G=F^K=>G=exp(K*ln(F))\)

void polyqsm(ll f[], int k, int n) {
    polyln(f, n);
    for (int i = 0; i < n; i++) f[i] = f[i] * k % mod;
    polyexp(f, n);
}

第一类斯特林数

将n个两两不同的元素,划分为k个非空圆排列的方案数记作\(c(n,k)或[^n_k]\)(无符号)

  • \(c(n,k)=c(n−1,k−1)+(n−1)*c(n−1,k)\)
  • \(x^{\frac{}{n}}=\sum_{k=0}^{n}c(n,k)x^k\)
  • 有符号:\(S_1(n,k)=(-1)^{n+k}c(n,k)\)
  • \(x^{\frac{n}{}}=\sum_{k=0}^nS_1(n,k)x^k\)
  • 关于n的指数生成函数:\(\sum_{n\geq 0}S_1(n,k)\frac{x^n}{n!}=\frac{1}{k!}(ln(1+x))^k\)
  • \(\sum_{n\geq 0}c(n,k)\frac{x^n}{n!}=\frac{1}{k!}(ln(\frac{1}{1-x}))^k\)

第二类斯特林数

把n个不同的小球放在m个相同的盒子里方案数,记为\(\{^n_k\}或S_2(n,k)\)

  • \(S_2(n,k)=S_2(n−1,k−1)+kS_2(n−1,k)\)
  • \(S_2(n,k)=\frac{1}{k!}\sum_{i=0}^{k}(-1)^i(^k_i)(k-i)^n\)
  • \(x^n=\sum_{k=0}^{n}S_2(n,k)x^{\frac{k}{}}\)
  • \(\sum_{x=a}^{b}x^{\frac{m}{}}=\frac{(b+1)^{\frac{m+1}{}}-(a+1)^{\frac{m+1}{}}}{m+1}\)
  • \(\{^n_k\}+(n-1)\{^{n-1}_k\}=\sum_{j=1}^{n}j(^{n-1}_{j-1})\{_{k-1}^{n-j}\}\)
  • 关于n的指数生成函数:\(\sum_{n\geq 0}S_2(n,k)\frac{x^n}{n!}=\frac{1}{k!}(exp(x)-1)^k\)

\[求第n行:\sum_{k=0}^{n}\{^n_k\}=f(x)g(x),[x^k]f(x)=(-1)^k\frac{1}{k!},[x^k]g(x)=\frac{1}{k!}k^n\\ 求第k列:\sum_{n=0}^{N}\{^n_k\}=g(x),g(x)=\frac{1}{k!}(exp(x)-1)^k \]

整数分拆

n个无标号的球分配到k个无标号的盒子的方案数,记作\(p(n,k)\)

  • 递推式:\(p(n,k)=p(n-1,k-1)+p(n-k,k)\)
  • 关于n的常生成函数:\(\sum_{n\geq 0}p(n,k)x^n=x^k\prod_{i=1}^{k}\frac{1}{1-x^i}\)

n个无标号的球分配到一些无标号的盒子的方案数,记作\(p(n)\)

  • \(p(n)=\sum_{k=1}^np(n,k)\)
  • 递推式:\(p(n)=\sum_{k\geq 1}(-1)^{k-1}(p(n-\frac{3k^2-k}{2})+p(n-\frac{3k^2+k}{2}))\)
  • 常生成函数:\(\sum_{n\geq 0}p(n)x^n=\prod_{i\geq 1}\frac{1}{1+x^i}\)

\[ln(\prod_{i\geq 1}^{k}\frac{1}{1+x^i})=\sum_{i=1}^k\sum_{j\geq 1}\frac{x^{ij}}{j} \]

分配问题

n个球 k个盒子 盒子可以为空 盒子不能为空
有标号 有标号 \(k^n\) \(k!S_2(n,k)\)
有标号 无标号 \(\sum_{i=1}^kS_2(n,i)\) \(S_2(n,k)\)
无标号 有标号 \(C(n+k-1,k-1)\) \(C(n-1,k-1)\)
无标号 无标号 \(p(n+k,k)\) \(p(n,k)\)

分配问题(加强版1)

把n个球放入k个盒子中,装有i个球的盒子有\(f_i\)种形态,不同形态算不同方案,问有多少方案?
\(设\{f_i\}_{i\geq 1}的常生成函数为F(x)=\sum_{i\geq 1}f_ix^i,指数生成函数为E(x)=\sum_{i\geq 1}f_i\frac{x^i}{i!},e.g.f代表指数生成函数,o.g.f表示常生成函数。\)

n个球 k个盒子 方案生成函数
有标号 有标号 \(e.g.f=E(x)^k\)
有标号 无标号 \(e.g.f=\frac{1}{k!}E(x)^k\)
无标号 有标号 \(o.g.f=F(x)^k\)

分配问题(加强版2)

把n个球放入一些盒子中,装有i个球的盒子有\(f_i\)种形态,不同形态算不同方案,问有多少方案?
\(设\{f_i\}_{i\geq 1}的常生成函数为F(x)=\sum_{i\geq 1}f_ix^i,指数生成函数为E(x)=\sum_{i\geq 1}f_i\frac{x^i}{i!},e.g.f代表指数生成函数,o.g.f表示常生成函数。\)

n个球 k个盒子 方案生成函数
有标号 有标号 \(e.g.f=\frac{1}{1-E(x)}\)
有标号 无标号 \(e.g.f=exp(E(x))\)
无标号 有标号 \(o.g.f=\frac{1}{1-F(x)}\)
无标号 无标号 \(o.g.f=\prod_{i\geq 1}(\frac{1}{1-x^i})^{f_i}=exp(\sum_{j\geq 1}\frac{1}{j}F(x^j))\)

置换群

群:满足结合律,有单位元,有逆元
置换:从该集合映射至自身的双射
G-轨道数量:等价类

Burnside引理

设有限群\((G, o)\)作用在有限集X上,则X上的G-轨道数量为

\[N=\frac{1}{|G|}\sum_{g\in G}\psi(g) \]

其中\(\psi(g)表示g(x)=x的x的数量。\)

n元置换旋转:\(\psi(g)=m^{gcd(n,i)}\)(可以从n除以i余数方面考虑)

置换群的轮换指标

轮换的形式:把置换中的每个环上的节点按顺序记录下来,它是置换的另一种表示形式,比如\(g=[3,4,5,6,1,2]=(135)(246)\)
置换型:如果n元置换g中有\(b_i\)个长度为i的轮换(其实就是环,\(1\leq i\leq n\)),则称这个置换g型为\(1^{b_i}2^{b_2}...n^{b_n}\)

  • 设(G, o)是一个n元置换的置换群,它的轮换指标为:

\[P_G(x_1,x_2,...x_n)=\frac{1}{|G|}\sum_{g\in G}x_1^{b_1}x_2^{b_2}...x_n^{b_n} \]

  • 正n边型的旋转群轮换指标:

\[P_G=\frac{1}{n}\sum_{d|n}\varphi(d) x^{n/d}_{d} \]

  • 正n边型的二面体群的轮换指标:

\[P_G=\frac{1}{2n}\sum_{d|n}\varphi(d)x_{d}^{n/d}+ \begin{cases} \frac{1}{2}x_1x_2^{\frac{n-1}{2}},&n为奇数\\ \frac{1}{4}(x_2^{\frac{n}{2}}+x_1^2x_2^{\frac{n-2}{2}}),&n为偶数\\ \end{cases} \]

关于正方体的置换群

  • 顶点置换群:

\[P_G=\frac{1}{24}(x_1^8+8x_1^2x_3^2+9x_2^4+6x_4^2) \]

  • 边置换群:

\[P_G=\frac{1}{24}(x_1^{12}+8x_3^4+6x_1^2x_2^5+3x_2^6+6x_4^3) \]

  • 面置换群:

\[P_G=\frac{1}{24}(x_1^6+8x_3^2+6x_2^3+3x_1^2x_2^2+6x_1^2x_4) \]

暴力枚举\(x_d^{sum}\)

ll get(int sum, int d) {
    ll s = 1;
    for (int i = 0; i < 3; i++) {
        if (f[i] % x != 0) return 0;
        s = s * C(sum, f[i] / x);
        sum -= f[i] / x;
    }
    return s;
}

定理(Pólya定理简化版)

  • 集合X可以看成是给集合\(A=\{a_1,a_2,...,a_n\}\)的每一个元素赋予式样(颜色,种类等)的映射的集合
  • 引入表示式样的集合B,令\(X=\{f|f:A\rightarrow B\}\),记作\(B^A\)
  • G在\(B^A\)上的作用:A上的置换群G到集合\(B^A\)的作用为:\(g(f):a\rightarrow f(g(a))\)(证明他是一个作用?)
  • 式样清单:G作用在\(B^A\)上的G-轨道的集合称为\(B^A\)关于G的式样清单

\(B^A\)关于G的样式清单记为F,则

\[|F|=P_G(|B|,|B|,...,|B|) \]

定理(Pólya定理(原版))

  • 种类的权值:假设B上的每个元素b都赋予了权值w(b)
  • \(f\in B^A\)的权值:定义\(w(f):=\prod_{a\in A}w(f(a))\)
  • G-轨道的权值:\(w(F):=w(f),任选一个f\in F\)

\(B^A\)关于G的式样清单记为\(F\),则:

\[\sum_{f\in F}w(f) = P_G(\sum_{b\in B}w(b),\sum_{b\in B}w(b)^2,...,\sum_{b\in B}w(b)^n) \]

集合幂级数

id(S):集合状态压缩后,S对应的编号。
给每个 \(S\in 2^A\)赋予一个权值w(S),则定义它关联的集合幂级数为 \(f(x)=\sum_{S\in 2^A}w(S)x^{id(S)}\)

  • 与、或、异或卷积和形式幂级数卷积类似

快速沃尔什变换(FWT)

\(A_0,A_1 多项式的前\frac{n}{2}位、后\frac{n}{2}位\)
\((A,B) 将多项式A,B前后拼接起来\)

  • 或卷积

\[FWT(A)= \begin{cases} (FWT(A_0),FWT(A_0+A_1))&n>1\\ A&n=0\\ \end{cases} \]

  • 与卷积

\[FWT(A)= \begin{cases} (FWT(A_0+A_1),FWT(A_0))&n>1\\ A&n=0\\ \end{cases} \]

  • 异或卷积

\[FWT(A)= \begin{cases} (FWT(A_0+A_1),FWT(A_0-A_1))&n>1\\ A&n=0\\ \end{cases} \]

const int P = 998244353;

void add(int &x, int y) {
    (x += y) >= P && (x -= P);
}
void sub(int &x, int y) {
    (x -= y) < 0 && (x += P);
}
struct FWT {
    int extend(int n) {
        int N = 1;
        for (; N < n; N <<= 1);
        return N;
    }
    void FWTor(std::vector<int> &a, bool rev) {
        int n = a.size();
        for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
            for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
                if (!rev) add(a[i + j + m], a[i + j]);
                else sub(a[i + j + m], a[i + j]);
            }
        }
    }
    void FWTand(std::vector<int> &a, bool rev) {
        int n = a.size();
        for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
            for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
                if (!rev) add(a[i + j], a[i + j + m]);
                else sub(a[i + j], a[i + j + m]);
            }
        }
    }
    void FWTxor(std::vector<int> &a, bool rev) {
        int n = a.size(), inv2 = (P + 1) >> 1;
        for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
            for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
                int x = a[i + j], y = a[i + j + m];
                if (!rev) {
                    a[i + j] = (x + y) % P;
                    a[i + j + m] = (x - y + P) % P;
                } else {
                    a[i + j] = 1LL * (x + y) * inv2 % P;
                    a[i + j + m] = 1LL * (x - y + P) * inv2 % P;
                }
            }
        }
    }
    std::vector<int> Or(std::vector<int> a1, std::vector<int> a2) {
        int n = std::max(a1.size(), a2.size()), N = extend(n);
        a1.resize(N), FWTor(a1, false);
        a2.resize(N), FWTor(a2, false);
        std::vector<int> A(N);
        for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
        FWTor(A, true);
        return A;
    }
    std::vector<int> And(std::vector<int> a1, std::vector<int> a2) {
        int n = std::max(a1.size(), a2.size()), N = extend(n);
        a1.resize(N), FWTand(a1, false);
        a2.resize(N), FWTand(a2, false);
        std::vector<int> A(N);
        for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
        FWTand(A, true);
        return A;
    }
    std::vector<int> Xor(std::vector<int> a1, std::vector<int> a2) {
        int n = std::max(a1.size(), a2.size()), N = extend(n);
        a1.resize(N), FWTxor(a1, false);
        a2.resize(N), FWTxor(a2, false);
        std::vector<int> A(N);
        for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
        FWTxor(A, true);
        return A;
    }
} fwt;
  • 其他运算
    \(f(i,x,y)表示第i位的x,y的运算的值,假如是0的话FWT(c_0)+=FWT(a_x)*FWT(b_y),反之FWT(c_1)+=FWT(a_x)*FWT(b_y),然后将FWT转换成点乘形式,最后对FWT(c)按位进行逆运算即可\)
void change(int &a0, int &a1, int &b0, int &b1, int d) {
    if (d == 0) { // c0 = (a0 + a1) * (b0 + b1), c1 = 0
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, 0, 0);
    } else if (d == 1) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a0 * b0
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a0, b0);
    } else if (d == 2) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a0 * b1
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a0, b1);
    } else if (d == 3) {// c0 = a1 * (b0 + b1), c1 = a0 * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a1, b0 + b1, a0, b0 + b1);
    } else if (d == 4) { // c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a1 * b0;
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a1, b0);
    } else if (d == 5) {// c0 = (a0 + a1) * b1, c1 = (a0 + a1) * b0
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b1, a0 + a1, b0);
    } else if (d == 6) {// c0 + c1 = (a0 + a1) * (b0 + b1), c0 - c1 = (a0 - a1) * (b0 - b1)
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a0 - a1, b0 - b1);
    } else if (d == 7) {// c0 = a1 * b1, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a1, b1, a0 + a1, b0 + b1);
    } else if (d == 8) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a1 * b1
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a1, b1);
    } else if (d == 9) { // c0 + c1 = (a0 + a1) * (b0 + b1), c0 - c1 = (a0 - a1) * (b1 - b0)
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0 + b1, a0 - a1, b1 - b0);
    } else if (d == 10) {// c0 = (a0 + a1) * b0, c1 = (a0 + a1) * b1;
        tie(a0, b0, a1, b1) = make_tuple(a0 + a1, b0, a0 + a1, b1);
    } else if (d == 11) {// c0 = a1 * b0, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a1, b0, a0 + a1, b0 + b1);
    } else if (d == 12) {// c0 = a0 * (b0 + b1), c1 = a1 * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a0, b0 + b1, a1, b0 + b1);
    } else if (d == 13) {// c0 = a0 * b1, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a0, b1, a0 + a1, b0 + b1);
    } else if (d == 14) {// c0 = a0 * b0, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(a0, b0, a0 + a1, b0 + b1);
    } else if (d == 15) {// c0 = 0, c1 = (a0 + a1) * (b0 + b1)
        tie(a0, b0, a1, b1) = make_tuple(0, 0, a0 + a1, b0 + b1);
    }
}
void Ichange(int &c0, int &c1, int d) {
    if (d == 1) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a0 * b0
        tie(c0, c1) = make_tuple(c0 - c1, c1);
    } else if (d == 2) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a0 * b1
        tie(c0, c1) = make_tuple(c0 - c1, c1);
    } else if (d == 4) { // c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a1 * b0;
        tie(c0, c1) = make_tuple(c0 - c1, c1);
    } else if (d == 6) {// c0 + c1 = (a0 + a1) * (b0 + b1), c0 - c1 = (a0 - a1) * (b0 - b1)
        tie(c0, c1) = make_tuple((c0 + c1) / 2, (c0 - c1) / 2);
    } else if (d == 7) {// c0 = a1 * b1, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(c0, c1) = make_tuple(c0, c1 - c0);
    } else if (d == 8) {// c0 + c1 = (a0 + a1) * (b0 + b1), c1 = a1 * b1
        tie(c0, c1) = make_tuple(c0 - c1, c1);
    } else if (d == 9) { // c0 + c1 = (a0 + a1) * (b0 + b1), c0 - c1 = (a0 - a1) * (b1 - b0)
        tie(c0, c1) = make_tuple((c0 + c1) / 2, (c0 - c1) / 2);
    } else if (d == 11) {// c0 = a1 * b0, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(c0, c1) = make_tuple(c0, c1 - c0);
    } else if (d == 13) {// c0 = a0 * b1, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(c0, c1) = make_tuple(c0, c1 - c0);
    } else if (d == 14) {// c0 = a0 * b0, c0 + c1 = (a0 + a1) * (b0 + b1)
        tie(c0, c1) = make_tuple(c0, c1 - c0);
    }
}

集合幂级数求幂\(f^k\)

  • 计算\(g=fwt(f)\)
  • 每项求\(h_i=g_i^k\)
  • 计算\(ifwt(h)\)

图论

二分图匹配

  • 最大匹配数 = 最小点覆盖数 = n - 最大独立集
bool find(int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (st[j])continue;
        st[j] = true;
        if (!match[j] || find(match[j])) {
            match[j] = u;
            return true;
        }
    }
    return false;
}

km算法

int n, pre[N], mat[N], vb[N];
ll cost[N][N], wa[N], wb[N], res, slack[N];

void bfs(int u) {
    fill(pre, pre + 1 + n, 0);
    fill(slack, slack + 1 + n, INF);
    slack[0] = 0;
    ll x = 0, y = 0, yy = 0, d = 0;
    mat[y] = u;
    while (true) {
        x = mat[y], d = INF, vb[y] = 1;
        for (int i = 1; i <= n; i++) {
            if (vb[i])continue;
            ll gap = wa[x] + wb[i] - cost[x][i];
            if (slack[i] > gap)slack[i] = gap, pre[i] = y;
            if (d > slack[i])d = slack[i], yy = i;
        }
        for (int i = 0; i <= n; i++) {
            if (vb[i])wa[mat[i]] -= d, wb[i] += d;
            else slack[i] -= d;
        }
        y = yy;
        if (mat[y] == -1)break;
    }
    while (y)mat[y] = mat[pre[y]], y = pre[y];
}

ll km() {
    fill(mat, mat + 1 + n, -1);
    for (int i = 1; i <= n; i++) {
        fill(vb, vb + 1 + n, 0);
        bfs(i);
    }
    for (int i = 1; i <= n; i++)res += cost[mat[i]][i];
    return res;
}

网络流

  • 最大流 = 最小割
// dinic
void add(int a, int b, int c, int d) {
    e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx++;
    e[idx] = a, ne[idx] = h[b], f[idx] = d, h[b] = idx++;
}
bool bfs() {
    memset(d, -1, sizeof(d));
    queue<int> que;
    que.push(S), d[S] = 0, cur[S] = h[S];
    while (!que.empty()) {
        int u = que.front(); que.pop();
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] == -1 && f[i]) {
                d[j] = d[u] + 1;
                cur[j] = h[j];
                if (j == T) return true;
                que.push(j);
            }
        }
    }
    return false;
}
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {
        cur[u] = i;
        int j = e[i];
        if (d[j] == d[u] + 1 && f[i]) {
            int t = find(j, min(f[i], limit - flow));
            if (!t) d[j] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}
int dinic() {
    int res = 0, flow;
    while (bfs()) while (flow = find(S, INF)) res += flow;
    return res;
}

费用流

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5010, M = 100010, INF = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];

void add(int a, int b, int c, int d)
{
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}

bool spfa()
{
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = INF;
    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (f[i] && d[ver] > d[t] + w[i])
            {
                d[ver] = d[t] + w[i];
                pre[ver] = i;
                incf[ver] = min(f[i], incf[t]);
                if (!st[ver])
                {
                    q[tt ++ ] = ver;
                    if (tt == N) tt = 0;
                    st[ver] = true;
                }
            }
        }
    }

    return incf[T] > 0;
}

void EK(int& flow, int& cost)
{
    flow = cost = 0;
    while (spfa())
    {
        int t = incf[T];
        flow += t, cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1])
        {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &S, &T);
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add(a, b, c, d);
    }

    int flow, cost;
    EK(flow, cost);
    printf("%d %d\n", flow, cost);

    return 0;
}

有向图的强连通分量

void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    stk.push(u), st[u] = true;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (st[j])low[u] = min(low[u], dfn[j]);
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        while (true) {
            int v = stk.top(); 
            stk.pop();
            scc[v] = scc_cnt, st[v] = false, sum[scc_cnt]++;
            if (u == v)break;
        }
    }
}

无向图的强连通分量

  • 无向连通图中,如果删除某点后,图变成不连通,则称该点为割点
  • 无向连通图中,如果删除某边后,图变成不连通,则称该边为桥
void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++cnt;
    stk.push(u);
    // int child = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            // i和i^1为割边
            // if (dfn[u] < low[j]) st[i] = st[i ^ 1] = true; 

            // u是割点
            // if (dfn[u] <= low[j]) {
            //     child++;
            //     if (u != root || child > 1) st[u] = true;
            // }
        } else if (fa != (i ^ 1)) {
            low[u] = min(low[u], dfn[j]);
        }
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        while (true) {
            int v = stk.top();
            stk.pop();
            scc[v] = scc_cnt;
            if (u == v)break;
        }
    }
}

lca

void bfs() {
    memset(depth, 0x3f, sizeof(depth));
    depth[0] = 0, depth[root] = 1;
    queue<int> que;
    que.push(root);
    while (que.size()) {
        int u = que.front();
        que.pop();
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (depth[j] > depth[u] + 1) {
                depth[j] = depth[u] + 1;
                que.push(j);
                fa[j][0] = u;
                for (int k = 1; k <= max_v; k++) {
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];
                }
            }
        }
    }
}
int lca(int a,int b) {
    if (depth[a] < depth[b]) swap(a, b);
    for (int k = max_v; k >= 0; k--) {
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    }
    if (a == b)return a;
    for (int k = max_v; k >= 0; k--) {
        if (fa[a][k] != fa[b][k]) {
            a = fa[a][k];
            b = fa[b][k];
        }
    }
    return fa[a][0];
}

平面图

欧拉公式

设连通平面图G的顶点数、边数和面数分别为n,m和r,则有\(n-m+r=2\)

计算几何

求某个点在三角形内部

#include <iostream>
#include <math.h>
using namespace std;
struct Point {
    double x;
    double y;
};
double product(Point p1,Point p2,Point p3) {
    //首先根据坐标计算p1p2和p1p3的向量,然后再计算叉乘
    //p1p2 向量表示为 (p2.x-p1.x,p2.y-p1.y)
    //p1p3 向量表示为 (p3.x-p1.x,p3.y-p1.y)
    return (p2.x-p1.x)*(p3.y-p1.y) - (p2.y-p1.y)*(p3.x-p1.x);
}
bool isInTriangle(Point p1,Point p2,Point p3,Point o) {
    //保证p1,p2,p3是逆时针顺序
    if(product(p1, p2, p3)<0) return isInTriangle(p1,p3,p2,o);
    if(product(p1, p2, o)>0 && product(p2, p3, o)>0 && product(p3, p1, o)>0)
        return true;
    return false;
}
int main() {
    Point p1,p2,p3,o;
    cin >> p1.x >> p1.y;
    cin >> p2.x >> p2.y;
    cin >> p3.x >> p3.y;
    cin >> o.x >> o.y;
    bool flag = isInTriangle(p1,p2,p3,o);
    if(flag) puts("Yes");
    else puts("No");
}

凸包

struct Point {
    double x, y;
    Point(double _x = 0, double _y = 0) : x(_x), y(_y) {};
    bool operator < (const Point &p) const {
        if (y * p.x == x * p.y) return fabs(x) + fabs(y) < fabs(p.x) + fabs(p.y);
        return y * p.x <= x * p.y;
    }
    double dis() {
        return hypot(x, y);
    }
    Point operator - (const Point &p) const {
        return Point{x - p.x, y - p.y};
    }
    Point &operator -=(const Point &p) {
        this->x -= p.x, this->y -= p.y;
        return *(this);
    }
};
using Points = vector<Point>;
namespace Convex_hull {
    Points Graham(Points &All) {
        Point o = All[0];
        for (auto &ths : All) {
            if (ths.y < o.y || ths.y == o.y && ths.x < o.x) o = ths;
        }
        for (auto &ths : All) ths -= o;
        sort(All.begin(), All.end());
        if (All.size() <= 2) return All;
        Points vec;
        for (auto &p : All) {
            int top = (int)vec.size() - 1;
            while (top >= 1 && (p - vec[top - 1]) < (vec[top] - vec[top - 1])) --top, vec.pop_back();
            vec.push_back(p);
        }
        return vec;
    }
}
posted @ 2022-09-04 17:43  什么都不会的娃娃  阅读(112)  评论(0)    收藏  举报