c++17;gnu++23;GoLang 的命名规范;0-indexed 的左闭右开区间;std::cin 和 std::cout
输入输出;using namespace std; 和
#define int long long;lambda
而非全局函数;emplace 而非 push,使用
contains 而非 count;vector 而非
map;不要求顺序的情况下可以用 unordered_
系列,但在 Codeforces 上记得使用随机模数;#include <bits/stdc++.h>
using namespace std;
#define int long long
int T{0};
void solve() {
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
if (!T)
cin >> T;
while (T--)
solve();
}using i8 = signed char;
using u8 = unsigned char;
using i32 = signed;
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;// std::mt19937
mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// splitmix64
struct custom_hash {
static u64 splitmix64(u64 x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
u64 operator()(u64 x) const {
static const u64 SEED =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
return splitmix64(x + SEED);
}
};int l, r, ans;
bool check(int);
while (l <= r) {
int mid = l + (r - l) / 2;
if (check(mid)) {
ans = mid;
// l 和 r 取决于方向
l = mid + 1;
} else {
r = mid - 1;
}
}// C++23
auto dfs1 = [&](this auto &&self, int x, int p) -> void {
for (auto y : adj[x]) {
if (y == p)
continue;
self(y, x);
}
};
// C++17
std::function<void(int, int)> dfs2 = [&](int x, int p) {
for (auto y : adj[x]) {
if (y == p)
continue;
dfs2(y, x);
}
};初始化 [2, N] 内质数和非质数标记。
pri 存储所有质数;np[x] 表示 x 是否不是质数;Sieve<N> sieve;
初始化;O(N)。template <int N>
struct Sieve {
std::vector<int> pri;
bool np[N + 1]{};
Sieve() {
np[0] = np[1] = true;
for (int i = 2; i <= N; i++) {
if (!np[i])
pri.emplace_back(i);
for (int p : pri) {
if (i * p > N)
break;
np[i * p] = true;
if (i % p == 0)
break;
}
}
}
};Factor(n) 返回质因数分解,Divisor(n)
返回大于 1 且小于等于 n 的约数。
int 范围内的试除;O(sqrt n)。inline auto Factor(int n) {
std::vector<std::pair<int, int>> res;
for (int i = 2; i * i <= n; i++) {
int cur = 0;
while (n % i == 0) {
cur++;
n /= i;
}
if (cur != 0)
res.emplace_back(i, cur);
}
if (n > 1)
res.emplace_back(n, 1);
return res;
}
inline auto Divisor(int n) {
std::vector<int> res;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
res.emplace_back(i);
if (i * i != n)
res.emplace_back(n / i);
}
}
std::sort(res.begin(), res.end());
return res;
}返回 gcd(a, b),并求出 ax + by = gcd(a, b)
的一组解。
O(log min(a, b))。inline auto ExGCD(auto a, auto b, auto &x, auto &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
auto d = ExGCD(b, a % b, y, x);
y -= a / b * x;
return d;
}计算 a^b mod p。
b >= 0;O(log b)。inline int PowMod(i64 a, i64 b, int p) {
i64 res = 1;
while (b) {
if (b & 1) res = res * a % p;
b = b >> 1;
a = a * a % p;
}
return res;
}基于 ModInt 预处理阶乘和逆阶乘。
Z 需要支持乘法、除法;C(n, k) 返回组合数;O(n),单次查询 O(1)。template <class Z>
struct Comb {
std::vector<Z> fac, ifac;
Comb(int n) : fac(n + 1), ifac(n + 1) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i - 1] * i;
ifac[n] = Z(1) / fac[n];
for (int i = n; i >= 1; i--)
ifac[i - 1] = ifac[i] * i;
}
Z C(int n, int k) const {
if (k < 0 or k > n)
return 0;
return fac[n] * ifac[k] * ifac[n - k];
}
Z A(int n, int k) const {
if (k < 0 or k > n)
return 0;
return fac[n] * ifac[n - k];
}
};维护无向连通性,Merge(x, y) 返回是否真的合并成功。
struct DSU {
std::vector<int> f;
DSU(int n) : f(n) { std::iota(f.begin(), f.end(), 0); }
int Find(int x) {
while (x != f[x])
x = f[x] = f[f[x]];
return x;
}
bool Merge(int x, int y) {
x = Find(x), y = Find(y);
if (x == y)
return false;
f[y] = x;
return true;
}
};维护单点加、前缀和、区间和。
1-indexed;Sum(l, r) 查询闭区间 [l, r];O(log n)。template <typename T> struct Fenwick {
int n;
std::vector<T> a;
Fenwick(int n) : n(n), a(n + 1) {}
void Add(int x, T v) {
for (; x <= n; x += x & -x)
a[x] += v;
}
T sum(int x) {
T res = {};
for (; x; x -= x & -x)
res += a[x];
return res;
}
T Sum(int l, int r) { return sum(r) - sum(l - 1); }
};维护单点修改、区间查询。
O(log n)。关于 S 的约束:
operator+ 运算;{}
(具有默认构造函数,且满足和单位元运算后不改变原值)关于初始区间:
S,则必须保证 S 存在对应构造函数。template <class S>
struct SegTree {
int n;
std::vector<S> tr;
SegTree(int n, const S &e) {
build(n, std::vector<S>(n, e));
}
template <std::random_access_iterator It>
SegTree(It l, It r) {
build(r - l, l);
}
void build(int m, auto &&arr) {
for (n = 1; n < m; n <<= 1);
tr.resize(n << 1);
for (int i = 0; i < m; i++)
tr[i + n] = arr[i];
for (int i = n - 1; i >= 1; i--)
pull(i);
}
void pull(int k) {
tr[k] = tr[k << 1] + tr[k << 1 | 1];
}
void Set(int p, const S &x) {
p += n;
tr[p] = x;
for (p >>= 1; p; p >>= 1)
pull(p);
}
S Get(int p) {
return tr[p + n];
}
S Query(int l, int r) {
l += n, r += n;
S sml{}, smr{};
while (l < r) {
if (l & 1)
sml = sml + tr[l++];
if (r & 1)
smr = tr[--r] + smr;
l >>= 1;
r >>= 1;
}
return sml + smr;
}
};使用非递归的实现方式。
O(log n)。关于 F 的约束:
operator+= 运算;{}(默认构造函数)。关于 S 的约束:
operator+ 运算;{}
(具有默认构造函数,且满足和单位元运算后不改变原值)operator*= 运算满足将映射 F 应用于
S 返回一个 S,并且满足分配律。关于初始区间:
S,则必须保证 S 存在对应构造函数。template<class S, class F>
struct LazySegTree {
int n, h;
std::vector<S> tr;
std::vector<F> lz;
LazySegTree(int n, const S &e) {
build(n, std::vector<S>(n, e));
}
template <std::random_access_iterator It>
LazySegTree(It l, It r) {
build(r - l, l);
}
void build(int m, auto &&arr) {
for (n = 1; n < m; n <<= 1);
h = __builtin_ctz(n);
tr.resize(n << 1);
lz.resize(n);
for (int i = 0; i < m; i++)
tr[i + n] = arr[i];
for (int i = n - 1; i >= 1; i--)
pull(i);
}
void apply(int k, const F &f) {
tr[k] *= f;
if (k < n)
lz[k] += f;
}
void pull(int k) {
tr[k] = tr[k << 1] + tr[k << 1 | 1];
}
void push(int k) {
apply(k << 1, lz[k]);
apply(k << 1 | 1, lz[k]);
lz[k] = {};
}
void Set(int p, const S &x) {
p += n;
for (int i = h; i >= 1; i--) push(p >> i);
tr[p] = x;
for (int i = 1; i <= h; i++) pull(p >> i);
}
S Get(int p) {
p += n;
for (int i = h; i >= 1; i--) push(p >> i);
return tr[p];
}
void Update(int l, int r, const F &f) {
l += n, r += n;
for (int i = h; i >= 1; i--) {
if ((l & ((1 << i) - 1)) != 0) push(l >> i);
if ((r & ((1 << i) - 1)) != 0) push((r - 1) >> i);
}
{
int l_ = l, r_ = r;
while (l < r) {
if (l & 1) apply(l++, f);
if (r & 1) apply(--r, f);
l >>= 1;
r >>= 1;
}
l = l_;
r = r_;
}
for (int i = 1; i <= h; i++) {
if ((l & ((1 << i) - 1)) != 0) pull(l >> i);
if ((r & ((1 << i) - 1)) != 0) pull((r - 1) >> i);
}
}
S Query(int l, int r) {
l += n, r += n;
for (int i = h; i >= 1; i--) {
if ((l & ((1 << i) - 1)) != 0) push(l >> i);
if ((r & ((1 << i) - 1)) != 0) push((r - 1) >> i);
}
S sml{}, smr{};
while (l < r) {
if (l & 1)
sml = sml + tr[l++];
if (r & 1)
smr = tr[--r] + smr;
l >>= 1;
r >>= 1;
}
return sml + smr;
}
};维护非负整数可重集,支持插入、删除和查询与 x
异或的最大值。
[30, 0] 位;O(31)。struct XorTrie {
std::vector<std::array<int, 2>> ch;
std::vector<int> cnt;
int tot = 1;
XorTrie(int n) : ch(n * 32), cnt(n * 32) {}
void Insert(int x) {
int u = 1;
cnt[u]++;
for (int i = 30; i >= 0; i--) {
int c = (x >> i) & 1;
if (not ch[u][c]) {
ch[u][c] = ++tot;
}
u = ch[u][c];
cnt[u]++;
}
}
void Erase(int x) {
int u = 1;
cnt[u]--;
for (int i = 30; i >= 0; i--) {
int c = (x >> i) & 1;
u = ch[u][c];
cnt[u]--;
}
}
int Query(int x) {
int res = 0;
int u = 1;
for (int i = 30; i >= 0; i--) {
int c = (x >> i) & 1;
int v = ch[u][c ^ 1];
if (v and cnt[v] > 0) {
u = v;
res |= (1 << i);
} else {
u = ch[u][c];
}
}
return res;
}
};固定模数整数,支持四则运算、输入输出和比较。
ModInt<P> 使用 unsigned 模数;u64 模数使用长双精度近似规避溢出。using u32 = unsigned;
using i64 = long long; using u64 = unsigned long long;
template <class T> constexpr T power(T a, u64 b, T res = 1) {
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template <u32 P> constexpr u32 mulMod(u32 a, u32 b) { return u64(a) * b % P; }
template <u64 P> constexpr u64 mulMod(u64 a, u64 b) {
u64 res = a * b - u64(1.L * a * b / P - 0.5L) * P;
res %= P;
return res;
}
constexpr i64 safeMod(i64 x, i64 m) {
x %= m;
if (x < 0) {
x += m;
}
return x;
}
constexpr std::pair<i64, i64> invGcd(i64 a, i64 b) {
a = safeMod(a, b);
if (a == 0) {
return {b, 0};
}
i64 s = b, t = a;
i64 m0 = 0, m1 = 1;
while (t) {
i64 u = s / t;
s -= t * u;
m0 -= m1 * u;
std::swap(s, t);
std::swap(m0, m1);
}
if (m0 < 0) {
m0 += b / s;
}
return {s, m0};
}
template <std::unsigned_integral U, U P> struct ModIntBase {
public:
constexpr ModIntBase() : x(0) {}
template <std::unsigned_integral T> constexpr ModIntBase(T x_) : x(x_ % mod()) {}
template <std::signed_integral T> constexpr ModIntBase(T x_) {
using S = std::make_signed_t<U>;
S v = x_ % S(mod());
if (v < 0) {
v += mod();
}
x = v;
}
constexpr static U mod() { return P; }
constexpr U val() const { return x; }
constexpr ModIntBase operator-() const {
ModIntBase res;
res.x = (x == 0 ? 0 : mod() - x);
return res;
}
constexpr ModIntBase inv() const { return power(*this, mod() - 2); }
constexpr ModIntBase &operator*=(const ModIntBase &rhs) & {
x = mulMod<mod()>(x, rhs.val());
return *this;
}
constexpr ModIntBase &operator+=(const ModIntBase &rhs) & {
x += rhs.val();
if (x >= mod()) {
x -= mod();
}
return *this;
}
constexpr ModIntBase &operator-=(const ModIntBase &rhs) & {
x -= rhs.val();
if (x >= mod()) {
x += mod();
}
return *this;
}
constexpr ModIntBase &operator/=(const ModIntBase &rhs) & { return *this *= rhs.inv(); }
friend constexpr ModIntBase operator*(ModIntBase lhs, const ModIntBase &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModIntBase operator+(ModIntBase lhs, const ModIntBase &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModIntBase operator-(ModIntBase lhs, const ModIntBase &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModIntBase operator/(ModIntBase lhs, const ModIntBase &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr std::istream &operator>>(std::istream &is, ModIntBase &a) {
i64 i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModIntBase &a) {
return os << a.val();
}
friend constexpr bool operator==(const ModIntBase &lhs, const ModIntBase &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr std::strong_ordering operator<=>(const ModIntBase &lhs,
const ModIntBase &rhs) {
return lhs.val() <=> rhs.val();
}
private:
U x;
};
template <u32 P> using ModInt = ModIntBase<u32, P>;
template <u64 P> using ModInt64 = ModIntBase<u64, P>;适用于非负边权图。
adj[u] 存储 (v, w);1..n;O((n + m) log n)。auto dijkstra(const auto &adj, int n, int s) {
vector<int> dis(n + 1, INF);
vector<bool> vis(n + 1, false);
priority_queue<pair<int, int>> pq;
dis[s] = 0;
pq.emplace(0, s);
while (!pq.empty()) {
auto [_, u] = pq.top(); pq.pop();
if (vis[u])
continue;
vis[u] = true;
for (auto [v, w] : adj[u]) {
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
pq.emplace(-dis[v], v);
}
}
}
return dis;
}适用于非负边权图上最多使用 k 次特殊操作。
P4568 的建模方式:
dist[u][i] 表示到达点 u,且已经用了
i 次免费机会的最小代价;(u, i) -> (v, i),边权为
w;i < k,则可以免费走这条边:(u, i) -> (v, i + 1),边权为
0;min(dist[t][0..k])。复杂度 O((n k + m k) log(n k))。
auto LayeredDijkstra(const auto &adj, int n, int s, int k) {
vector dist(n + 1, vector<i64>(k + 1, INF));
priority_queue<tuple<i64, int, int>> pq;
dist[s][0] = 0;
pq.emplace(0, s, 0);
while (!pq.empty()) {
auto [d, u, used] = pq.top(); pq.pop();
d = -d;
if (d != dist[u][used])
continue;
for (auto [v, w] : adj[u]) {
if (dist[u][used] + w < dist[v][used]) {
dist[v][used] = dist[u][used] + w;
pq.emplace(-dist[v][used], v, used);
}
if (used < k and dist[u][used] < dist[v][used + 1]) {
dist[v][used + 1] = dist[u][used];
pq.emplace(-dist[v][used + 1], v, used + 1);
}
}
}
return dist;
}Tarjan 求有向图强连通分量。
1..n;id[u] 为 u 所在 SCC 编号;O(n + m)。struct SCC {
int n, now = 0, cnt = 0;
vector<vector<int>> adj;
vector<int> dfn, low, stk, ins, id;
SCC(int n) : n(n), adj(n + 1), dfn(n + 1), low(n + 1), ins(n + 1), id(n + 1) {}
void AddEdge(int u, int v) {
adj[u].emplace_back(v);
}
void Tarjan(int u) {
dfn[u] = low[u] = ++now;
stk.emplace_back(u);
ins[u] = true;
for (int v : adj[u]) {
if (!dfn[v]) {
Tarjan(v);
low[u] = min(low[u], low[v]);
} else if (ins[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
cnt++;
while (true) {
int x = stk.back();
stk.pop_back();
ins[x] = false;
id[x] = cnt;
if (x == u)
break;
}
}
}
pair<vector<int>, int> Work() {
for (int i = 1; i <= n; i++)
if (!dfn[i])
Tarjan(i);
return {id, cnt};
}
};Tarjan 求无向图割点。
1..n;O(n + m)。vector<int> CutVertex(const vector<vector<int>> &adj, int n) {
vector<int> dfn(n + 1), low(n + 1), cut(n + 1), res;
int now = 0;
auto Dfs = [&](this auto &&self, int u, int p) -> void {
dfn[u] = low[u] = ++now;
int child = 0;
for (int v : adj[u]) {
if (!dfn[v]) {
child++;
self(v, u);
low[u] = min(low[u], low[v]);
if (p != 0 and low[v] >= dfn[u])
cut[u] = true;
} else if (v != p) {
low[u] = min(low[u], dfn[v]);
}
}
if (p == 0 and child >= 2)
cut[u] = true;
};
for (int i = 1; i <= n; i++)
if (!dfn[i])
Dfs(i, 0);
for (int i = 1; i <= n; i++)
if (cut[i])
res.emplace_back(i);
return res;
}匈牙利算法。
1..n,右部点 1..m;adj[u] 存储左部点 u 能匹配的右部点;O(nm),稀疏图通常够用。int BipartiteMatching(const vector<vector<int>> &adj, int n, int m) {
vector<int> mt(m + 1), vis(m + 1);
int ans = 0, stamp = 0;
auto dfs = [&](this auto &&self, int u) -> bool {
for (int v : adj[u]) {
if (vis[v] == stamp)
continue;
vis[v] = stamp;
if (!mt[v] or self(mt[v])) {
mt[v] = u;
return true;
}
}
return false;
};
for (int i = 1; i <= n; i++) {
stamp++;
ans += dfs(i);
}
return ans;
}Kahn 算法。
1..n;n,则图中有环;O(n + m)。vector<int> TopoSort(const vector<vector<int>> &adj, int n) {
vector<int> indeg(n + 1), res;
queue<int> q;
for (int u = 1; u <= n; u++)
for (int v : adj[u])
indeg[v]++;
for (int i = 1; i <= n; i++)
if (!indeg[i])
q.emplace(i);
while (!q.empty()) {
int u = q.front();
q.pop();
res.emplace_back(u);
for (int v : adj[u])
if (--indeg[v] == 0)
q.emplace(v);
}
return res;
}Bellman-Ford 判负环并求一组可行解。
x[v] <= x[u] + w;edges 存储 (u, v, w);vector<long long> DifferenceConstraints(int n, const vector<array<int, 3>> &edges) {
vector<long long> d(n + 1);
for (int i = 1; i <= n; i++) {
bool changed = false;
for (auto [u, v, w] : edges) {
if (d[v] > d[u] + w) {
d[v] = d[u] + w;
changed = true;
}
}
if (!changed)
break;
if (i == n)
return {};
}
return d;
}求前缀函数,支持模式串匹配。
Kmp(s)[i] 表示 s[0..i) 的 border
长度;O(n + m)。vector<int> Kmp(const string &s) {
int n = s.size();
vector<int> f(n + 1);
for (int i = 1, j = 0; i < n; i++) {
while (j > 0 and s[i] != s[j])
j = f[j];
j += (s[i] == s[j]);
f[i + 1] = j;
}
return f;
}复数 FFT 求整数多项式卷积。
a.size() + b.size() - 1 的结果;O(n log n)。using comp = complex<double>;
const double PI = acos(-1);
void Fft(vector<comp> &a, bool inv) {
int n = a.size();
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1)
j ^= bit;
j ^= bit;
if (i < j)
swap(a[i], a[j]);
}
for (int len = 2; len <= n; len <<= 1) {
double ang = 2 * PI / len * (inv ? -1 : 1);
comp wlen(cos(ang), sin(ang));
for (int i = 0; i < n; i += len) {
comp w(1);
for (int j = 0; j < len / 2; j++) {
comp u = a[i + j];
comp v = a[i + j + len / 2] * w;
a[i + j] = u + v;
a[i + j + len / 2] = u - v;
w *= wlen;
}
}
}
if (inv)
for (auto &x : a)
x /= n;
}
vector<long long> Convolution(const vector<long long> &a, const vector<long long> &b) {
if (a.empty() or b.empty())
return {};
int need = a.size() + b.size() - 1;
int n = 1;
while (n < need)
n <<= 1;
vector<comp> fa(a.begin(), a.end()), fb(b.begin(), b.end());
fa.resize(n);
fb.resize(n);
Fft(fa, false);
Fft(fb, false);
for (int i = 0; i < n; i++)
fa[i] *= fb[i];
Fft(fa, true);
vector<long long> res(need);
for (int i = 0; i < need; i++)
res[i] = llround(fa[i].real());
return res;
}