数据结构
树状数组
//单点修改+区间查询 或 区间修改+单点查询
struct bit {
int n;
vector<int> a;
bit(int _n) : n(_n) {
a.assign(n + 1, 0);
}
void ad(int p, int v) {
for (; p <= n; p += p & -p) {
a[p] += v;
}
}
int qr(int p) {
int sum = 0;
for (; p; p -= p & -p) {
sum += a[p];
}
return sum;
}
int sum(int l, int r) {
return qr(r) - qr(l - 1);
}
};
区间修改+区间查询
struct BIT {
int n;
vector<int> b1, b2;
BIT(int _n) {
n = _n;
b1.assign(n+1,0);
b2.assign(n+1,0);
}
inline int lowbit(int x) {
return x & -x;
}
void update(int p, int v) {
for (int i = p; i <= n; i += lowbit(i)) {
b1[i] += v;
b2[i] += p*v;
}
}
void update(int l, int r, int v) {
update(l,v);
update(r+1,-v);
}
int query(int p) {
int sum = 0;
for (int i = p; i; i -= lowbit(i)) {
sum += (p+1) * b1[i] - b2[i];
}
return sum;
}
int query(int l, int r) {
return query(r) - query(l-1);
}
};
并查集
struct DSU {
vector<int> f, sz;
DSU(int n) {
f.resize(n + 1);
iota(f.begin(), f.end(), 0);
sz.assign(n + 1, 1);
}
int find(int x) {
if (f[x] != x) {
f[x] = find(f[x]);
}
return f[x];
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
f[y] = x;
return true;
}
};
单调栈
//O(n) 求离 a[i] 最近的第一个 大于/小于(等于) a[i] 的下标
stack<int> stk;
a[0] = a[n + 1] = 1e9;
stk.push(0);
for (int i = 1; i <= n; i++) {
while (stk.size() && a[i] > a[stk.top()]) stk.pop();
L[i] = stk.top() + 1;
stk.push(i);
}
while (stk.size()) stk.pop();
stk.push(n + 1);
for (int i = n; i >= 1; i--) {
while (stk.size() && a[i] >= a[stk.top()]) stk.pop();
R[i] = stk.top() - 1;
stk.push(i);
}
单调队列
// 求区间长度为k的最大值
int L = 1, R = 0; // [L,R]
for (int i = 1; i <= n; i++) {
while (R - L + 1 >= 1 && a[i] < a[qmini[R]]) {
R--;
}
qmini[++R] = i;
if (i > k && i - k >= qmini[L]) {
L++;
}
if (i >= k) cout << a[qmini[L]] << " ";
}
ST表
int st[N][__lg(N) + 1];
int lg[N];
void solve() {
int n,m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> st[i][0];
}
lg[1] = 0;
for (int i = 2; i <= n; i++) {
lg[i] = lg[i >> 1] + 1;
}
for (int j = 1; j <= lg[n]; j++) {
for (int i = 1; i+(1<<(j-1)) <= n; i++) {
st[i][j] = max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
while(m--) {
int l, r;
cin >> l >> r;
int k = lg[r-l+1];
cout << max(st[l][k],st[r-(1<<k)+1][k]) << "\n";
}
}
线段树
struct tag {
tag() {}
void apply(tag& t) {
}
};
struct nd {
nd() {}
nd operator+ (const nd& b) const {
}
void apply(tag t) {
}
};
struct lazy {
int n;
vector<nd> a;
vector<tag> tg;
lazy(int _n, vector<nd>& b) : n(_n) {
a.assign(4 * n, nd());
tg.assign(4 * n, tag());
auto bd = [&](auto&& self, int p, int l, int r) -> void {
if (l == r) {
a[p] = b[l];
return;
}
int m = l + r >> 1;
self(self, p << 1, l, m);
self(self, p << 1 | 1, m + 1, r);
up(p);
};
bd(bd, 1, 1, n);
}
inline void up(int p) {
a[p] = a[p << 1] + a[p << 1 | 1];
}
void apply(int p, tag& t) {
a[p].apply(t);
tg[p].apply(t);
}
void down(int p) {
apply(p << 1, tg[p]);
apply(p << 1 | 1, tg[p]);
tg[p] = tag();
}
void modify(int p, int l, int r, int x, int y, tag& t) {
if (r < x || l > y) {
return;
}
if (l >= x && r <= y) {
apply(p, t);
return;
}
down(p);
int m = l + r >> 1;
modify(p << 1, l, m, x, y, t);
modify(p << 1 | 1, m + 1, r, x, y, t);
up(p);
}
nd qr(int p, int l, int r, int x, int y) {
if (r < x || l > y) {
return nd();
}
if (l >= x && r <= y) {
return a[p];
}
int m = l + r >> 1;
down(p);
return qr(p << 1, l, m, x, y) + qr(p << 1 | 1, m + 1, r, x, y);
}
};
仅单点修改
void modify(int x, nd& t) {
x = mp[x];
a[x] = t;
while(x/2) up(x);
}
线段树上二分
int qr_pos(int p, int l, int r, int x, int y, long long& k) { // 查询 [x,y] 区间前缀第一个大于等于k的位置
if (r < x || l > y) return -1;
if (l >= x && r <= y) {
if (a[p].sum < k) {
k -= a[p].sum;
return -1;
}
if (l == r) return l;
}
down(p);
int m = (l + r) >> 1;
int res = qr_pos(p << 1, l, m, x, y, k);
if (res != -1) return res;
return qr_pos(p << 1 | 1, m + 1, r, x, y, k);
}
可持久化
查询区间 [L,R] 中第k大的数
#include <bits/stdc++.h>
using namespace std;
struct PresidentTree
{
static constexpr int N = 2e5 + 10;
int cntNodes = 0, root[N]; // root[i] 表示前i个元素构成的版本
struct Node
{
int l = 0, r = 0; // 左右子节点编号(初始为0)
int cnt = 0; // 当前区间的元素个数
};
vector<Node> tr;
PresidentTree() { tr.resize(20 * N); }
// 构建初始空树
void build_empty(int &u, int l, int r)
{
u = ++cntNodes;
tr[u] = Node();
if (l == r)
return;
int mid = (l + r) >> 1;
build_empty(tr[u].l, l, mid);
build_empty(tr[u].r, mid + 1, r);
}
// 插入数值 x,返回新版本的根
void modify(int &u, int v, int l, int r, int x)
{
u = ++cntNodes;
tr[u] = tr[v];
tr[u].cnt++;
if (l == r)
return;
int mid = (l + r) >> 1;
if (x <= mid)
modify(tr[u].l, tr[v].l, l, mid, x);
else
modify(tr[u].r, tr[v].r, mid + 1, r, x);
}
// 查询第k小
int kth(int u, int v, int l, int r, int k)
{
if (l == r)
return l;
int left_cnt = tr[tr[v].l].cnt - tr[tr[u].l].cnt;
int mid = (l + r) >> 1;
if (k <= left_cnt)
return kth(tr[u].l, tr[v].l, l, mid, k);
else
return kth(tr[u].r, tr[v].r, mid + 1, r, k - left_cnt);
}
};
signed main()
{
ios::sync_with_stdio(0), cin.tie(0);
int n, q;
cin >> n >> q;
vector<int> a(n + 1), b(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i];
}
sort(b.begin() + 1, b.end());
auto ed = unique(b.begin() + 1, b.end());
int m = ed - (b.begin() + 1);
PresidentTree tree;
tree.build_empty(tree.root[0], 1, m); // 初始空树 root[0]
// 构建前缀版本:遍历 a[1..n]
for (int i = 1; i <= n; ++i) {
int x = lower_bound(b.begin() + 1, ed, a[i]) - (b.begin() + 1) + 1;
tree.modify(tree.root[i], tree.root[i - 1], 1, m, x);
}
while (q--) {
int l, r, k;
cin >> l >> r >> k;
int rank = tree.kth(tree.root[l - 1], tree.root[r], 1, m, k);
cout << b[rank] << "\n";
}
return 0;
}
重链剖分
树拆成多条重链,然后放到线段树上维护,可以lg^2解决一条路径的询问
重儿子:子树中size最大的儿子,每个节点只有一个重儿子,其余为轻儿子
重链:从一个节点开始依次访问重儿子
https://www.luogu.com.cn/problem/P3384
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define int long long
const int N = 1e5 + 10;
int n, q, r, mod, id = 1, cnt = 0;
int head[N], v[N], to[2 * N], nx[2 * N], fa[N], dep[N], dfn[N], son[N], tp[N], sz[N], sz2[4*N];
void ad(int x, int y) {
to[id] = y;
nx[id] = head[x];
head[x] = id++;
}
void dfs1(int cur, int f) {
dep[cur] = dep[f] + 1;
fa[cur] = f;
sz[cur] = 1;
for (int i = head[cur]; i > 0; i = nx[i]) {
int u = to[i];
if (u == f) {
continue;
}
dfs1(u, cur);
if (sz[u] > sz[son[cur]]) {
son[cur] = u;
}
sz[cur] += sz[u];
}
}
void dfs2(int cur, int f) {
cnt++;
dfn[cur] = cnt;
tp[cur] = f;
if (son[cur] == 0) return;
dfs2(son[cur], f);
for (int i = head[cur]; i > 0; i = nx[i]) {
int u = to[i];
if (u == fa[cur] || u == son[cur]) {
continue;
}
dfs2(u, u);
}
}
int tree[4 * N], tag[4 * N];
void up(int p) {
tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % mod;
}
void build(int p, int l, int r, vector<int>& b) {
if (l == r) {
tree[p] = b[l];
sz2[p] = 1;
return;
}
int mid = l + r >> 1;
build(p << 1, l, mid, b);
build(p << 1 | 1, mid + 1, r, b);
up(p);
sz2[p] = r - l + 1;
}
void apply(int p, int v) {
tree[p] = (tree[p] + v*sz2[p] % mod) % mod;
tag[p] = (tag[p] + v) % mod;
}
void down(int p) {
apply(p << 1, tag[p]);
apply(p << 1 | 1, tag[p]);
tag[p] = 0;
}
void update(int ul, int ur, int v, int p, int l, int r) {
if (l > ur || r < ul) return;
if (l >= ul && r <= ur) {
apply(p, v);
return;
}
down(p);
int mid = l + r >> 1;
update(ul, ur, v, p << 1, l, mid);
update(ul, ur, v, p << 1 | 1, mid + 1, r);
up(p);
}
void update(int ul, int ur, int v) {
update(ul, ur, v, 1, 1, n);
}
int query(int ql, int qr, int p, int l, int r) {
if (l > qr || r < ql) return 0;
if (l >= ql && r <= qr) {
return tree[p] % mod;
}
down(p);
int mid = l + r >> 1;
return (query(ql, qr, p << 1, l, mid) + query(ql, qr, p << 1 | 1, mid + 1, r)) % mod;
}
int query(int ql, int qr) {
return query(ql, qr, 1, 1, n);
}
void add(int x, int y, int v) {
while (tp[x] != tp[y]) {
if (dep[tp[x]] < dep[tp[y]]) {
swap(x, y);
}
update(dfn[tp[x]],dfn[x],v);
x = fa[tp[x]];
}
if (dep[x] > dep[y]) {
swap(x,y);
}
update(dfn[x],dfn[y],v);
}
int sum(int x, int y) {
int res = 0;
while (tp[x] != tp[y]) {
if (dep[tp[x]] < dep[tp[y]]) {
swap(x, y);
}
res = (res + query(dfn[tp[x]],dfn[x])) % mod;
x = fa[tp[x]];
}
if (dep[x] > dep[y]) {
swap(x,y);
}
res = (res + query(dfn[x],dfn[y])) % mod;
return res;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cin >> n >> q >> r >> mod;
vector<int> a(n + 1), b(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
ad(u, v);
ad(v, u);
}
dfs1(r, 0);
dfs2(r, r);
for (int i = 1; i <= n; i++) {
b[dfn[i]] = a[i];
}
build(1,1,n,b);
int op, x, y, z;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x >> y >> z;
add(x,y,z);
} else if (op == 2) {
cin >> x >> y;
cout << sum(x,y) << '\n';
} else if (op == 3) {
cin >> x >> z;
update(dfn[x], dfn[x] + sz[x] - 1, z);
} else {
cin >> x;
cout << query(dfn[x], dfn[x] + sz[x] - 1) << '\n';
}
}
return 0;
}
ODT 颜色段均摊
struct odt {
struct node {
int l, r;
mutable int v;
node(int _l, int _r = -1,int _v = 0):l(_l), r(_r),v(_v) {}
bool operator< (const node& b) const {
return l < b.l;
};
};
set<node> s;
odt() { s.clear(); }
auto split(int pos) {
auto it = s.lower_bound(node(pos));
if (it != s.end() && it->l == pos) return it;
it--;
int l = it->l, r = it->r, v = it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
auto assign(int l, int r, int v) {
auto itr = split(r+1), itl = split(l);
s.erase(itl,itr);
s.insert(node(l,r,v));
}
void add(int l, int r, int x) {
auto itr = split(r+1), itl = split(l);
for (auto it = itl; it != itr; it++) {
it->v += x;
}
}
int kth(int l, int r,int k) {
auto itr = split(r+1), itl = split(l); // 不能交换顺序,如果先左后右左边的迭代器被删除会RE
vector<pair<int,int>> tmp;
for (auto it = itl; it != itr; it++) {
tmp.push_back({it->v,it->r - it->l+1});
}
sort(tmp.begin(),tmp.end());
for (auto i : tmp) {
k -= i.second;
if (k <= 0) {
return i.first;
}
}
}
int ksm(int a, int b, int mod) {
a %= mod;
int ans = 1;
while (b > 0) {
if (b&1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans;
}
int po(int l, int r, int x, int y) {
auto itr = split(r+1), itl = split(l);
int tot = 0;
for (auto it = itl; it != itr; it++) {
tot = (tot + ((it->r - it->l + 1) % y) * ksm(it->v,x,y)) % y;
}
return tot;
}
};
带修莫队
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define int long long
const int N = 1e6 + 10;
int n, q, sz, cc = 0, sum = 0;
struct nd {
int id, l, r, t;
} a[N];
pair<int,int> qr[N];
int cnt[N], ans[N], num[N];
bool cmp (const nd& a, const nd&b) {
if (a.l / sz != b.l / sz) return a.l / sz < b.l / sz;
if (a.r / sz != b.r / sz) return a.r / sz < b.r / sz;
return a.t < b.t;
}
void ad(int x) {
sum += (cnt[x] == 0);
cnt[x]++;
}
void del(int x) {
sum -= (cnt[x] == 1);
cnt[x]--;
}
void up(int x, int t) {
if (qr[t].first >= a[x].l && qr[t].first <= a[x].r) {
del(num[qr[t].first]);
ad(qr[t].second);
}
swap(num[qr[t].first],qr[t].second);
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> num[i];
}
sz = pow(n,0.666);
int len = 0;
for (int i = 1; i <= q; i++) {
char c;
int l, r;
cin >> c >> l >> r;
if (c == 'Q') {
len++;
a[len].id = len;
a[len].l = l, a[len].r = r;
a[len].t = cc;
} else {
cc++;
qr[cc].first = l, qr[cc].second = r;
}
}
sort(a+1,a+len+1,cmp);
int l = 1, r = 0, t = 0;
for (int i = 1; i <= len; i++) {
while (l < a[i].l) del(num[l++]);
while (l > a[i].l) ad(num[--l]);
while (r < a[i].r) ad(num[++r]);
while (r > a[i].r) del(num[r--]);
while (t < a[i].t) up(i,++t);
while (t > a[i].t) up(i,t--);
ans[a[i].id] = sum;
}
for (int i = 1; i <= len; i++) {
cout << ans[i] << '\n';
}
return 0;
}
二维树状数组 区间 + 单点
struct BIT {
int n, m;
vector<vector<int>> a;
BIT(int _n, int _m) {
n = _n, m = _m;
a.assign(n+1,vector<int>(m+1));
}
inline int lowbit(int x) {
return x & -x;
}
void update(int x, int y, int v) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j+= lowbit(j)) {
a[i][j] += v;
}
}
}
void update(int x1, int y1, int x2, int y2, int v) {
update(x1,y1,v);
update(x1,y2+1,-v);
update(x2+1,y1,-v);
update(x2+1,y2+1,v);
}
int query(int x, int y) {
int sum = 0;
for (int i = x; i; i -= lowbit(i)) {
for (int j = y; j; j -= lowbit(j)) {
sum += a[i][j];
}
}
return sum;
}
int query(int x1, int y1, int x2, int y2) {
return query(x2,y2) - query(x2,y1-1) - query(x1-1,y2) + query(x1-1,y1-1);
}
};
二维树状数组区间修改+区间查询
struct BIT {
int n, m;
vector<vector<int>> b[4];
BIT(int _n, int _m) {
n = _n, m = _m;
for (int i = 0; i < 4; i++) {
b[i].assign(n + 1, vector<int>(m + 1));
}
}
inline int lowbit(int x) {
return x & -x;
}
void update(int x, int y, int v) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
b[0][i][j] += v;
b[1][i][j] += x * v;
b[2][i][j] += y * v;
b[3][i][j] += x * y * v;
}
}
}
void update(int x1, int y1, int x2, int y2, int v) {
update(x1, y1, v);
update(x1, y2 + 1, -v);
update(x2 + 1, y1, -v);
update(x2 + 1, y2 + 1, v);
}
int query(int x, int y) {
int sum = 0;
for (int i = x; i; i -= lowbit(i)) {
for (int j = y; j; j -= lowbit(j)) {
sum += (x + 1) * (y + 1) * b[0][i][j];
sum -= (x + 1) * b[2][i][j];
sum -= (y + 1) * b[1][i][j];
sum += b[3][i][j];
}
}
return sum;
}
int query(int x1, int y1, int x2, int y2) {
return query(x2, y2) + query(x1 - 1, y1 - 1) - query(x1 - 1, y2) - query(x2, y1 - 1);
}
};
数论
GCD
int gcd(int a, int b) {
return a % b == 0 ? b : gcd(b, a % b);
}
int lcm(int a,int b) {
return a / gcd(a, b) * b;
}
筛
埃氏筛
ll countPrimes(ll n) {
bool vis[n + 1];
for (int i = 0; i <= n; i++) vis[i] = false;
for (ll i = 2; i <= n; i++) {
if (!vis[i]) {
for (ll j = i * i; j <= n; j += i) {
vis[j] = true;
}
}
}
ll cnt = 0;
for (ll i = 2; i <= n; i++) {
if (!vis[i]) {
cnt++;
}
}
return cnt;
}
int gcd(int a, int b) {
return a % b == 0 ? b : gcd(b, a % b);
}
欧拉筛
vector<int> pri;
vector<bool> not_prime(N,false);
void pre(int n) {
for (int i = 2; i <= n; ++i) {
if (!not_prime[i]) {
pri.push_back(i);
}
for (int j : pri) {
if (i * j > n) break;
not_prime[i * j] = true; // i*j 以 j 为最小因子被筛除
if (i % j == 0) break; // 这样以后的数必然能被j筛除,以后再筛
}
}
}
快速幂
const int mod = 1e9 + 10;
int ksm(int a,int b) {
int ans = 1;
while (b > 0) {
if (b & 1) {
ans = ans * a % mod;
}
a = a*a%mod;
b >>= 1;
}
return ans;
}
int inv(int x) {
return ksm(x,mod-2);
}
组合数
卢卡斯定理:
const int N = 100000;
int fac[N + 5], inv[N + 5]; // 阶乘和阶乘的逆元
// 计算 a 的 b 次方模 mod
int mod = 1e9 + 7;
int ksm(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}
// 预处理前n阶乘和阶乘逆元
void pre(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = (fac[i - 1] * i) % mod;
}
inv[n] = ksm(fac[n], mod - 2);
for (int i = n - 1; i >= 0; i--) {
inv[i] = (inv[i + 1] * (i + 1)) % mod;
}
}
int C(int n, int m) {
if (m > n) return 0; // 不合法情况
return (fac[n] * inv[m] % mod * inv[n - m] % mod);
}
int Lucas(int n, int m) {
if (m == 0) return 1;
return (C(n % mod, m % mod) * Lucas(n / mod, m / mod) % mod);
}
矩阵运算,逆矩阵,矩阵快速幂
const int sz = 2;
struct Mat {
ll M[sz + 5][sz + 5];
void clear() { memset(M, 0, sizeof(M)); }
void reset() { // 初始化
clear();
for (int i = 1; i <= sz; ++i) M[i][i] = 1;
}
Mat friend operator*(const Mat &A, const Mat &B) {
Mat Ans;
Ans.clear();
for (int i = 1; i <= sz; ++i)
for (int j = 1; j <= sz; ++j)
for (int k = 1; k <= sz; ++k)
Ans.M[i][j] = (Ans.M[i][j] + A.M[i][k] * B.M[k][j]) % mod;
return Ans;
}
Mat friend operator+(const Mat &A, const Mat &B) {
Mat Ans;
Ans.clear();
for (int i = 1; i <= sz; ++i)
for (int j = 1; j <= sz; ++j)
Ans.M[i][j] = (A.M[i][j] + B.M[i][j]) % mod;
return Ans;
}
};
inline int mypow(ll n, ll k, int p = mod) {
ll r = 1;
for (; k; k >>= 1, n = n * n % p) {
if (k & 1) r = r * n % p;
}
return r;
}
inline Mat mypow(Mat n, ll k) {
Mat r;
r.reset();
for (; k; k >>= 1, n = n * n) {
if (k & 1) r = r * n;
}
return r;
}
bool ok = 1;
Mat getinv(Mat a) { // 矩阵求逆
int n = sz, m = sz * 2;
for (int i = 1; i <= n; i++) a.M[i][i + n] = 1;
for (int i = 1; i <= n; i++) {
int pos = i;
for (int j = i + 1; j <= n; j++)
if (abs(a.M[j][i]) > abs(a.M[pos][i])) pos = j;
if (i != pos) swap(a.M[i], a.M[pos]);
if (!a.M[i][i]) {
puts("No Solution");
ok = 0;
}
ll inv = mypow(a.M[i][i], mod - 2);
for (int j = 1; j <= n; j++)
if (j != i) {
ll mul = a.M[j][i] * inv % mod;
for (int k = i; k <= m; k++)
a.M[j][k] = ((a.M[j][k] - a.M[i][k] * mul) % mod + mod) % mod;
}
for (int j = 1; j <= m; j++) a.M[i][j] = a.M[i][j] * inv % mod;
}
Mat res;
res.clear();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
res.M[i][j] = a.M[i][n + j];
return res;
}
字符串
字符串哈希
base 进制下
$s[1] * base^{n-1} … s[n-1] * base ^ {1} + s[n] * base ^ {0}$
常用base: 31, 131, 13331
质数: 999999929, 999999937
记得开双模, 单模和自然溢出容易被卡
/* 1-index */
const int mod = 999999937;
const int base = 131;
ll ha[n+1];
ll p[n+1];
p[0] = 1, ha[0] = 0;
for(int i = 1; i <= len; i++) {
p[i] = p[i-1]*base;
ha[i] = ha[i-1]*base + s[i];
}
auto cal = [&] (int l, int r) -> int {
return (ha[r] - ha[l-1] * p[r-l+1] % mod + mod) % mod;
}
双模
static mt19937_64 rng(random_device{}());
int rd(int l, int r) {
return rng() % (r - l + 1) + l;
}
const int mod1 = rd((int)1e9,(int)2e9), mod2 = rd((int)1e9,(int)2e9);
struct Hash {
int b1 = 17, b2 = 233, n;
vector<int> f1, f2, B1, B2;
Hash() {}
Hash(string s) : n(s.length()), f1(n + 1), f2(n + 1), B1(n + 1), B2(n + 1) {
B1[0] = B2[0] = 1;
for (int i = 1; i <= n; i++) {
B1[i] = B1[i - 1] * b1 % mod1;
B2[i] = B2[i - 1] * b2 % mod2;
f1[i] = (f1[i - 1] * b1 + s[i - 1]) % mod1;
f2[i] = (f2[i - 1] * b2 + s[i - 1]) % mod2;
}
}
pair<int, int> get(int l, int r) {
int a1 = ((f1[r] - f1[l - 1] * B1[r - l + 1]) % mod1 + mod1) % mod1;
int a2 = ((f2[r] - f2[l - 1] * B2[r - l + 1]) % mod2 + mod2) % mod2;
return {a1, a2};
}
};
KMP
nxt[i] 表示 pre[i] 的最长非平凡border长度
失配后利用
s 的 border的border仍是 s 的border性质
加速匹配
string s1, s2;
cin >> s1 >> s2;
int n = s1.size(), m = s2.size();
s1 = '.' + s1, s2 = '.' + s2;
vector<int> nxt(m + 1);
nxt[1] = 0;
for (int i = 2, j = 0; i <= m; i++) {
j = nxt[i - 1];
while (j && s2[j + 1] != s2[i]) {
j = nxt[j];
}
if (s2[i] == s2[j + 1]) j++;
nxt[i] = j;
}
for (int i = 1, j = 0; i <= n; i++) {
while (j && s1[i] != s2[j + 1]) {
j = nxt[j];
}
if (s1[i] == s2[j + 1]) j++;
if (j == m) {
cout << i - m + 1 << '\n';
j = nxt[j];
}
}
扩展KMP
vector<int> zf(string& c) {
int len = c.size();
vector<int> nxt(len);
int p = 0, k = 1, l; //我们会在后面先逐位比较出 nxt[1] 的值,这里先设 k 为 1
//如果 k = 0,p 就会锁定在 |c| 不会被更改,无法达成算法优化的效果啦
nxt[0] = len; //以 c[0] 开始的后缀就是 c 本身,最长公共前缀自然为 |c|
while(p + 1 < len && c[p] == c[p + 1]) p++;
nxt[1] = p; //先逐位比较出 nxt[1] 的值
for(int i = 2; i < len; i++) {
p = k + nxt[k] - 1; //定义
l = nxt[i - k]; //定义
if(i + l <= p) nxt[i] = l; //如果灰方框小于初始的绿方框,直接确定 nxt[i] 的值
else {
int j = max(0LL, p - i + 1);
while(i + j < len && c[i + j] == c[j]) j++; //否则进行逐位比较
nxt[i] = j;
k = i; //此时的 x + nxt[x] - 1 一定刷新了最大值,于是我们要重新赋值 k
}
}
return nxt;
}
vector<int> zf(string& a, string& b, vector<int>& nxt) {
int la = a.size(), lb = b.size();
vector<int> ext(la);
int p = 0, k = 0, l;
while(p < la && p < lb && a[p] == b[p]) p++; //先算出初值用于递推
ext[0] = p;
for(int i = 1; i < la; i++) {
p = k + ext[k] - 1;
l = nxt[i - k];
if(i + l <= p) ext[i] = l;
else
{
int j = max(0LL, p - i + 1);
while(i + j < la && j < lb && a[i + j] == b[j]) j++;
ext[i] = j;
k = i;
}
}
return ext;
}
Manacher
p[i] 是包括了中心的回文半径
p[i] - 1 才是以回文长度
string s;
cin >> s;
int n = s.size();
string t = "-#";
for (int i = 0; i < n; i++) {
t += s[i];
t += '#';
}
int m = t.size();
t += '+';
int mid = 0, r = 0;
vector<int> p(m);
int ans = 0;
for (int i = 1; i < m; i++) {
p[i] = i < r ? min(p[2 * mid - i], r - i) : 1;
while (t[i - p[i]] == t[i + p[i]]) p[i]++;
if (i + p[i] > r) {
r = i + p[i];
mid = i;
}
}
trie
struct trie {
int n, cnt;
vector<vector<int>> nex;
vector<bool> f;
trie(int _n) : cnt(0), n(_n) {
nex.assign(n+1,vector<int>(26,0));
f.assign(n+1,0);
}
void insert(string& s) {
int p = 0;
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'a';
if (!nex[p][c]) nex[p][c] = ++cnt;
p = nex[p][c];
}
f[p] = true;
}
bool find(string& s) {
int p = 0;
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'a';
if (!nex[p][c]) return 0;
p = nex[p][c];
}
return f[p];
}
};
可持久化 0-1 trie
char op;
const int N = 6e5 + 10;
int val[N * 30];
int nx[N * 30][2];
int cnt = 0;
int rt[N], a[N];
void ins(int p, int lst, int v) {
for (int i = 30; i >= 0; i--) {
bool c = ((1 << i) & v);
if (!nx[p][c]) nx[p][c] = ++cnt;
nx[p][!c] = nx[lst][!c];
p = nx[p][c];
lst = nx[lst][c];
val[p] = val[lst] + 1;
}
}
int qr(int l, int r, int v) {
int ans = 0;
for (int i = 30; i >= 0; i--) {
bool c = (1 << i) & v;
if (val[nx[r][!c]] - val[nx[l][!c]]) {
ans |= 1 << i;
l = nx[l][!c], r = nx[r][!c];
}
else {
l = nx[l][c], r = nx[r][c];
}
}
return ans;
};
cnt = 0
for (int i = 0; i <= cnt; i++) {
val[i] = 0;
nx[i][0] = nx[i][1] = 0;
}
AC 自动机
struct AC {
int c[N][26];
int fail[N];
int val[N];
int id = 1;
void insert(string& s) {
int p = 0;
for (int i = 0; i < s.size(); i++) {
if (!c[p][s[i]-'a']) c[p][s[i]-'a']=id++;
p = c[p][s[i]-'a'];
}
val[p]++;
}
void build() {
queue<int> q;
for(int i=0;i<26;i++) {
if (c[0][i]) {
fail[c[0][i]]=0,q.push(c[0][i]);
}
}
while(!q.empty()) {
int cur=q.front();
q.pop();
for(int i=0;i<26;i++) {
if(c[cur][i]) {
fail[c[cur][i]]=c[fail[cur]][i],q.push(c[cur][i]);
} else {
c[cur][i]=c[fail[cur]][i]; // 虚 fail指针
}
}
}
}
int query(string& s){
int p=0,ans=0;
for(int i=0; i < s.size(); i++){
p=c[p][s[i]-'a'];
for(int t=p; t && ~val[t];t = fail[t]) {
ans+=val[t],val[t]=-1;
}
}
return ans;
}
};
图论
建图
邻接表
const int N = 2e5+10;
vector<vector<pair<int,int>>> G[N];
void addedge(int x,int y,int v) {
G[x].push_back(make_pair(y,v));
G[y].push_back(make_pair(x,v));
}
for(auto& i : adj[x]) {
}
前向星
vector<int> head(n+1), nx(m+1), to(m+1);
int id = 1;
auto ad = [&] (int x, int y) {
to[id] = y;
nx[id] = head[x];
head[x] = id++;
};
for(int i = head[x]; i > 0; i = nx[i]) {
}
拓扑排序
DAG 一定有
queue<int> q;
for (int i = 1; i <= n; i++) {
if (deg[i] == 0) {
q.push(i);
}
}
while (q.size()) {
int t = q.front();
q.pop();
cout << t << " ";
for (int i = head[t]; i > 0; i = nx[i]) {
int tt = to[i];
if (--deg[tt] == 0) {
q.push(tt);
}
}
}
Dijstra
#include <bits/stdc++.h>
using namespace std;
const long long inf = 2147483647;
struct edge {
int to, next, w;
} e[1001000];
int cnt = 0;
int head[1001000];
void addedge(int x, int y, int v) {
e[++cnt].next = head[x];
e[cnt].to = y;
e[cnt].w = v;
head[x] = cnt;
}
struct Node {
int dis;
int id;
bool operator <(const Node& b)const { //重载比较运算符,可以直接放进优先队列
return dis > b.dis;
}
};
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);//数据多加快读
int n, m, s; // 三个整数n,m,s分别表示点的个数、有向边的个数、出发点的编号
cin >> n >> m >> s;
long long dist[n + 1];
bool vis[n + 1];
for (int i = 1; i <= n; i++) {
dist[i] = inf;
vis[i] = false;
}
dist[s] = 0;
/*使用前向星*/
for (int i = 1; i <= m; i++) {
int begin, end, v;
cin >> begin >> end >> v;
addedge(begin, end, v);
}
priority_queue<Node> q;
q.push((Node) {0,s});
while (!q.empty()) {
Node temp = q.top();
q.pop();
if(vis[temp.id]) continue;
vis[temp.id] = true;
int cur = temp.id;
for (int j = head[cur]; j != 0; j = e[j].next) {
int to = e[j].to;
if (!vis[to] && dist[cur] + e[j].w < dist[to]) {
dist[to] = dist[cur] + e[j].w;
q.push((Node){dist[to],to});
}
}
}
for (int i = 1; i <= n; i++)
cout << dist[i] << " ";
return 0;
}
Floyd
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) {
dis[i][j] = 0;
} else {
dis[i][j] = inf;
}
}
}
for(int k = 1; k <= n; k++) { //途径k
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);
}
}
}
SPFA
const int inf = 1e16;
int dis[N], vis[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
cin >> n >> m >> s;
for (int i = 1; i <= m; i++) {
int x, y, w;
cin >> x >> y >> w;
ad(x, y, w);
}
for (int i = 1; i <= n; i++) {
dis[i] = inf;
}
dis[s] = 0;
queue<int> q;
q.push(s);
while (q.size()) {
int cur = q.front();
q.pop();
vis[cur] = false;
for (int i = head[cur]; i; i = nx[i]) {
if (dis[cur] + v[i] < dis[to[i]]) {
dis[to[i]] = dis[cur] + v[i];
if (!vis[to[i]]) {
q.push(to[i]);
vis[to[i]] = true;
}
}
}
}
return 0;
}
树的直径
两遍DFS求深度最大点
结论:树上一点到树上另一点的最远点为树的直径的一个端点
倍增LCA
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
//#define int ll
const int N = 5e5 + 10;
int head[N], nx[N*2], to[N*2];
int fa[N][35];
int dep[N];
int id = 1;
int n, m, s;
void add(int x, int y) {
to[id] = y;
nx[id] = head[x];
head[x] = id++;
}
void dfs(int now, int f) {
fa[now][0] = f;
dep[now] = dep[f] + 1;
for (int i = head[now]; i; i = nx[i]) {
if (to[i] == f) continue;
dfs(to[i], now);
}
}
int LCA(int x, int y) {
if (dep[x] < dep[y]) {
swap(x,y);
}
while (dep[x] > dep[y]) {
x = fa[x][__lg(dep[x] - dep[y])];
}
if (x == y) return x; // 节点相同就返回本身,否则就错误返回父亲节点
for (int i = 30; i >= 0; i--) {
if (fa[x][i] != fa[y][i]) {
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1; i <= n-1; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dep[0] = 0;
dfs(s,0);
fa[s][0] = s;
for (int i = 1; i <= 21; i++) {
for (int j = 1; j <= n; j++) {
fa[j][i] = fa[fa[j][i-1]][i-1];
}
}
while (m--) {
int a, b;
cin >> a >> b;
cout << LCA(a, b) << "\n";
}
return 0;
}
强联通分量缩点(SCC)
自带拓扑序
struct SCC {
int n, now, cnt;
vector<vector<int>> ver;
vector<int> dfn, low, col, stk;
SCC(int n) : n(n), ver(n + 1), low(n + 1) {
dfn.resize(n + 1, -1);
col.resize(n + 1, -1);
now = cnt = 0;
}
void add(int x, int y) {
ver[x].push_back(y);
}
void tarjan(int x) {
dfn[x] = low[x] = now++;
stk.push_back(x);
for (auto y : ver[x]) {
if (dfn[y] == -1) {
tarjan(y);
low[x] = min(low[x], low[y]);
} else if (col[y] == -1) {
low[x] = min(low[x], dfn[y]);
}
}
if (dfn[x] == low[x]) {
int pre;
cnt++;
do {
pre = stk.back();
col[pre] = cnt;
stk.pop_back();
} while (pre != x);
}
}
auto work() { // cnt 新图的顶点数量
for (int i = 1; i <= n; i++) { // 避免图不连通
if (dfn[i] == -1) {
tarjan(i);
}
}
vector<int> siz(cnt + 1); // siz 每个 scc 中点的数量
vector<vector<int>> adj(cnt + 1);
for (int i = 1; i <= n; i++) {
siz[col[i]]++;
for (auto j : ver[i]) {
int x = col[i], y = col[j];
if (x != y) {
adj[x].push_back(y);
}
}
}
return make_tuple(cnt, adj, col, siz);
}
//SCC scc(n);
//auto [cnt, adj, col, siz] = scc.work(); C++17 结构化绑定
};
最小生成树
const int MAXN = 2e5 + 1;
struct Node {
int x,y,v;
} a[MAXN];
int fa[5001];
int find(int x) {
if(fa[x] != x) {
x = find(fa[x]);
}
return fa[x];
}
bool merge(int x,int y) {
int fx = find(x),fy = find(y);
if(fx != fy) {
fa[fx] = fy;
return true;
} else {
return false;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n,m;
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> a[i].x >> a[i].y >> a[i].v;
}
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
sort(a+1,a+m+1,[](Node a,Node b) {return a.v < b.v;});
int cnt = 0;
int ans = 0;
for (int i = 1; i <= m; i++) {
if(merge(a[i].x,a[i].y)) {
cnt++;
ans += a[i].v;
}
}
if(cnt == n - 1) {
cout<<ans;
} else {
cout<<"orz";
}
cout<<endl;
return 0;
}
其他
二分
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
r = mid - 1;
ans = mid;
} else {
l = mid + 1;
}
}
二维差分前缀和
void add(int x1, int y1, int x2, int y2, int v) {
sum[x1][y1] += v;
sum[x2 + 1][y2 + 1] += v;
sum[x2 + 1][y1] -= v;
sum[x1][y2 + 1] -= v;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; i <= m; j++) {
sum[i][j] += sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1];
}
}
int query(int x1 ,int y1 ,int x2, int y2) {
return sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1];
}
等差数列差分
[L,R] 加上一段等差数列
做两次差分
然后最后做两遍前缀和还原
void op(int l, int r, int s, int e,int d) {
ans[l] += s;
ans[l + 1] += d - s;
ans[r + 1] += -e - d;
ans[r + 2] += e;
}
背包DP
01背包
每种物品只能选择一次,设有n种物品,每种物品的价值为wi,体积为vi,背包的容量为V。 ans[i][j]代表有前i个物品,空间为j时的价值最大值。 取选第i个物品与不选第i个物品的较大值。
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= V; j++) {
if (j < v[i]) {
ans[i][j] = ans[i - 1][j];
} else {
ans[i][j] = max(ans[i - 1][j], ans[i - 1][j - v[i]] + w[i]);
}
}
}
Copy
滚动数组压缩空间
从后往前更新
for(int i = 1; i <= n; i++) {
for(int j = V; j >= v[i]; j--) {
ans[j] = max(ans[j],ans[j-v[i]] + w[i]);
}
}
Copy
完全背包
每种物品可以取任意次。 转移方程和01背包类似
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= V; j++) {
if (j < v[i]) {
ans[i][j] = ans[i - 1][j];
} else {
ans[i][j] = max(ans[i - 1][j], ans[i][j - v[i]] + w[i]);
}
}
}
滚动数组压缩空间
从前往后更新
for(int i = 1; i <= n; i++) {
for(int j = v[i]; j <= V; j++) {
ans[j] = max(ans[j],ans[j-v[i]] + w[i]);
}
}
n维背包
有多个考虑因素
以二维01背包为例,增加条件背包最大承重为M,每一件物品的质量为mi。
for (int i = 1; i <= n; i++)//放置第i件物品
{
for (int j = 1; j <= V; j++)//j为当前体积
{
for (int k = 1; k <= M; k++)//k为当前质量
{
if (j - v[i] < 0 || k - m[i] < 0) { //任意一种条件不满足就放不进去
a[i][j][k] = a[i - 1][j][k];
} else {
a[i][j][k] = max(a[i - 1][j][k], a[i - 1][j - v[i]][k - m[i]] + c[i]);
}
}
}
}
滚动数组压缩空间
for (int i = 1; i <= n; i++) {
for (int j = V; j >= v[i]; j--) { // j为当前体积
for (int k = M; k >= m[i]; k--) { // k为当前质量
b[j][k] = max(b[j][k], b[j - v[i]][k - m[i]] + c[i]);
}
}
}
杂项
头
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define dbg(...) _((char*)#__VA_ARGS__,__VA_ARGS__)
template<typename t> void _(char* p,t x){cout<<p<<'='<<x<<'\n';}
template<typename t,typename... a>
void _(char* p,t x,a... y){while(*p!=',')cout<<*p++;cout<<'='<<x<<',';_(p+1,y...);}
void solve() {
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
对拍
//输入输出重定向
// wa.cpp, 存放待寻找错误的代码
freopen("rd.txt","r",stdin);
freopen("wa.out","w",stdout);
// ac.cpp, 存放暴力或正确的代码
freopen("rd.txt","r",stdin);
freopen("ac.out","w",stdout);
// rd.cpp
freopen("rd.txt", "w", stdout);
//随机数生成
static mt19937_64 rng(random_device{}());
int r(int l, int r) {
return uniform_int_distribution<int>(l,r)(rng); // rng() % (r - l + 1) + l;
}
//对拍器
#include<bits/stdc++.h>
using namespace std;
int main() {
int i=1;
int n = 100;
while (i <= n) { //一直循环,直到找到不一样的数据
// cout<<i<<endl;
system("rd.exe");
system("wa.exe");
system("ac.exe");
if (system("fc ac.out wa.out")) { //当 fc 返回 1 时,说明这时数据不一样
cout << "WA" << endl;
return 0;
}
i++;
}
cout << n << "组数据"<< "OK" <<'\n';
return 0;
}
int128
using i128 = __int128_t;
auto& operator>>(istream& it, __int128_t& j) {
string val;
it >> val;
reverse(val.begin(), val.end());
__int128_t ans = 0;
bool f = 0;
char c = val.back();
val.pop_back();
for (; c < '0' || c > '9'; c = val.back(), val.pop_back()) {
if (c == '-') {
f = 1;
}
}
for (; c >= '0' && c <= '9'; c = val.back(), val.pop_back()) {
ans = ans * 10 + c - '0';
}
j = f ? -ans : ans;
return it;
}
auto& operator<<(ostream& os, const __int128_t& j) {
string ans;
function<void(__int128_t)> write = [&](__int128_t x) {
if (x < 0) ans += '-', x = -x;
if (x > 9) write(x / 10);
ans += x % 10 + '0';
};
write(j);
return os << ans;
}
关闭同步流版本
#define int __int128_t
// 快读 __int128
inline int read() {
int x = 0;
bool neg = false;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') neg = true, c = getchar();
while (c >= '0' && c <= '9') {
x = x*10 + (c-'0');
c = getchar();
}
return neg ? -x : x;
}
// 快写 __int128
inline void write(int x) {
if (x == 0) { putchar('0'); return; }
if (x < 0) { putchar('-'); x = -x; }
char buf[40]; // __int128 最多 39 位
int i = 0;
while (x) {
buf[i++] = '0' + (int)(x % 10);
x /= 10;
}
while (i--) putchar(buf[i]);
}
取模机
using i64 = long long;
using u64 = unsigned long long;
// 特化的 2^61-1 模数类
struct Mod61 {
static constexpr u64 M = (1ull << 61) - 1;
u64 v;
Mod61(u64 x = 0) : v(x) {
v = (v & M) + (v >> 61);
v = v < M ? v : v - M;
}
static u64 mul(u64 a, u64 b) {
u64 l = (u32)a * (u32)b;
u64 m = (u32)a * (b >> 32) + (u32)b * (a >> 32);
u64 h = (a >> 32) * (b >> 32);
u64 t = (l & M) + (l >> 61) + (h << 3) + (m >> 29) + ((u32)m << 32 >> 3) + 1;
t = (t & M) + (t >> 61);
return t - 1;
}
Mod61 operator+(Mod61 o) const {
u64 s = v + o.v;
return {s < M ? s : s - M};
}
Mod61 operator-(Mod61 o) const {
return {v >= o.v ? v - o.v : M + v - o.v};
}
Mod61 operator*(Mod61 o) const {
return {mul(v, o.v)};
}
Mod61& operator+=(Mod61 o) {
v += o.v;
if (v >= M) v -= M;
return *this;
}
Mod61& operator-=(Mod61 o) {
v = v >= o.v ? v - o.v : M + v - o.v;
return *this;
}
Mod61& operator*=(Mod61 o) {
v = mul(v, o.v);
return *this;
}
bool operator==(Mod61 o) const { return v == o.v; }
bool operator!=(Mod61 o) const { return v != o.v; }
Mod61 pow(u64 exp) const {
Mod61 res(1), base(*this);
for (; exp; exp /= 2) {
if (exp & 1) res *= base;
base *= base;
}
return res;
}
Mod61 inv() const {
return pow(M - 2);
}
Mod61 operator/(Mod61 o) const {
return *this * o.inv();
}
Mod61& operator/=(Mod61 o) {
return *this *= o.inv();
}
friend std::ostream& operator<<(std::ostream& os, Mod61 m) {
return os << m.v;
}
friend std::istream& operator>>(std::istream& is, Mod61& m) {
u64 x;
is >> x;
m = Mod61(x);
return is;
}
u64 val() const { return v; }
};
/** 取模类(MLong & MInt 新版)
* 2023-08-14: https://ac.nowcoder.com/acm/contest/view-submission?submissionId=63433564
*
* 根据输入内容动态修改 MOD 的方法:Z::setMod(p) 。
**/
template<class T>
constexpr T power(T a, i64 b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
constexpr i64 mul(i64 a, i64 b, i64 p) {
i64 res = a * b - i64(1.L * a * b / p) * p;
res %= p;
if (res < 0) {
res += p;
}
return res;
}
template<int P>
struct MInt {
int x;
constexpr MInt() : x{} {}
constexpr MInt(i64 x) : x{norm(x % getMod())} {}
static int Mod;
constexpr static int getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(int Mod_) {
Mod = Mod_;
}
constexpr int norm(int x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr int val() const {
return x;
}
explicit constexpr operator int() const {
return x;
}
constexpr MInt operator-() const {
MInt res;
res.x = norm(getMod() - x);
return res;
}
constexpr MInt inv() const {
assert(x != 0);
return power(*this, getMod() - 2);
}
constexpr MInt &operator*=(MInt rhs) & {
x = 1LL * x * rhs.x % getMod();
return *this;
}
constexpr MInt &operator+=(MInt rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MInt &operator-=(MInt rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MInt &operator/=(MInt rhs) & {
return *this *= rhs.inv();
}
friend constexpr MInt operator*(MInt lhs, MInt rhs) {
MInt res = lhs;
res *= rhs;
return res;
}
friend constexpr MInt operator+(MInt lhs, MInt rhs) {
MInt res = lhs;
res += rhs;
return res;
}
friend constexpr MInt operator-(MInt lhs, MInt rhs) {
MInt res = lhs;
res -= rhs;
return res;
}
friend constexpr MInt operator/(MInt lhs, MInt rhs) {
MInt res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
i64 v;
is >> v;
a = MInt(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
return os << a.val();
}
friend constexpr bool operator==(MInt lhs, MInt rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MInt lhs, MInt rhs) {
return lhs.val() != rhs.val();
}
};
// template<>
// int MInt<0>::Mod = 998244353;
// template<int V, int P>
// constexpr MInt<P> CInv = MInt<P>(V).inv();
constexpr int P = 998244353;
using Z = MInt<P>;