算法模板
对拍
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数列:
- 递推式:\(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
- \(\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公式:
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,得到的迭代方程为\)
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,得到的迭代方程为\)
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\)
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)\)
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个无标号的球分配到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}\)
分配问题
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-轨道数量为
其中\(\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元置换的置换群,它的轮换指标为:
- 正n边型的旋转群轮换指标:
- 正n边型的二面体群的轮换指标:
关于正方体的置换群
- 顶点置换群:
- 边置换群:
- 面置换群:
暴力枚举\(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,则
定理(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\),则:
集合幂级数
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前后拼接起来\)
- 或卷积
- 与卷积
- 异或卷积
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;
}
}