寫在前面
好困啊,不想寫題,來寫板子了。
基礎
快讀
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) {
if (ch == '-') {
f = -1;
}
}
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
隨機樹生成器
#include <bits/stdc++.h>
const int kTreeSize = 100000;
int main() {
srand(time(0));
int n = kTreeSize;
printf("%d\n", n);
for (int i = 2; i <= n; ++ i) {
printf("%d %d\n", i, rand() % (i - 1) + 1);
}
return 0;
}
隨機圖生成器
保證無自環重邊。
#include <bits/stdc++.h>
#define pr std::pair
#define mp std::make_pair
const int kNodeSize = 10;
const int kEdgeSize = 20;
std::set <pr <int, int> > has;
int main() {
srand(time(0));
int n = kNodeSize, m = kEdgeSize;
printf("%d %d\n", n, m);
for (int i = 1; i <= m; ++ i) {
int u_ = rand() % n + 1, v_ = rand() % n + 1;
while (u_ == v_ || has.count(mp(u_, v_))) {
u_ = rand() % n + 1, v_ = rand() % n + 1;
}
has.insert(mp(u_, v_));
printf("%d %d\n", u_, v_);
}
return 0;
}
Checker
#include <bits/stdc++.h>
int main() {
for (int i = 1; ; i ++) {
printf("%d:", i);
system("datamaker.exe");
system("force.exe");
system("std.exe");
if (system("fc std.in force.in")) {
break;
}
}
}
數據結構
並查集
將集合關系轉化為圖論中連通塊的方式進行維護。
路徑壓縮 + 按秩合並
均攤復雜度 \(O(m)\)。
//知識點:並查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
//=============================================================
int n, m, fa[kMaxn], size[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
int fu = Find(u_), fv = Find(v_);
if (fu == fv) return ;
if (size[fu] > size[fv]) std::swap(fu, fv);
fa[fu] = fv;
size[fv] += size[fu];
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) {
fa[i] = i;
size[i] = 1;
}
for (int i = 1; i <= m; ++ i) {
int opt = read(), x = read(), y = read();
if (opt == 1) {
Union(x, y);
} else {
printf("%c\n", Find(x) == Find(y) ? 'Y' : 'N');
}
}
return 0;
}
可撤銷並查集
記錄操作的變化,壓入棧中。
為保證回退次數,只能使用按秩合並。
//知識點:可撤銷並查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, top, fa[kMaxn], size[kMaxn];
struct Stack {
int u, v, fa, size;
} st[kMaxm];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
int fu = Find(u_), fv = Find(v_);
if (size[fu] > size[fv]) std::swap(fu, fv);
st[++ top] = (Stack) {fu, fv, fa[fu], size[fu]};
if (fu != fv) {
fa[fu] = fv;
size[fv] += size[fu];
size[fu] = 0;
}
}
void Restore() {
Stack now = st[top];
if (now.u != now.v) {
fa[now.u] = now.fa;
size[now.v] -= now.size;
size[now.u] = now.size;
}
top --;
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) {
fa[i] = i;
size[i] = 1;
}
for (int i = 1; i <= m; ++ i) {
int opt = read(), x = read(), y = read();
if (opt == 1) {
Union(x, y);
} else {
printf("%c\n", Find(x) == Find(y) ? 'Y' : 'N');
}
}
return 0;
}
單調棧
求每個元素右側第一個比它大的數的位置。
維護一個單調遞減的棧。
//知識點:單調棧
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 3e6 + 10;
//=============================================================
int n, top, a[kMaxn], st[kMaxn], ans[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = 1; i <= n; ++ i) {
while (top && a[st[top]] < a[i]) {
ans[st[top]] = i;
top --;
}
st[++ top] = i;
}
for (int i = 1; i <= n; ++ i) printf("%d ", ans[i]);
return 0;
}
單調隊列
滑動窗口求區間最大值。
//知識點:單調隊列
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, k, a[kMaxn];
int h, t, q[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Init() {
h = t = 0;
}
void InsertMin(int pos_) {
while (t >= h && a[q[t]] >= a[pos_]) {
t --;
}
q[++ t] = pos_;
}
void InsertMax(int pos_) {
while (t >= h && a[q[t]] <= a[pos_]) {
t --;
}
q[++ t] = pos_;
}
int Query(int pos_) {
while (t >= h && q[h] + k <= pos_) ++ h;
return a[q[h]];
}
//=============================================================
int main() {
n = read(), k = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = 1; i < k; ++ i) InsertMin(i);
for (int i = k; i <= n; ++ i) {
InsertMin(i);
printf("%d ", Query(i));
}
printf("\n");
Init();
for (int i = 1; i < k; ++ i) InsertMax(i);
for (int i = k; i <= n; ++ i) {
InsertMax(i);
printf("%d ", Query(i));
}
return 0;
}
樹狀數組
二進制優化前綴和,維護前綴信息。
若想維護區間信息必須具有可減性。
單點加區間和
//知識點:樹狀數組
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
namespace Bit {
#define lowbit(x) (x&-x)
int Lim;
LL t[kMaxn];
void Init(int lim_) {
Lim = lim_;
}
void Insert(int pos_, LL val_) {
for (int i = pos_; i <= Lim; i += lowbit(i)) {
t[i] += val_;
}
}
LL Sum(int pos_) {
LL ret = 0;
for (int i = pos_; i; i -= lowbit(i)) {
ret += t[i];
}
return ret;
}
#undef lowbit
}
//=============================================================
int main() {
n = read(), m = read();
Bit::Init(n);
for (int i = 1; i <= n; ++ i) {
a[i] = read();
Bit::Insert(i, a[i]);
}
while (m --) {
int opt = read();
if (opt == 1) {
int x = read(), k = read();
Bit::Insert(x, 1ll * k);
} else {
int x = read(), y = read();
printf("%lld\n", Bit::Sum(y) - Bit::Sum(x - 1));
}
}
return 0;
}
ST 表
用長度為 2 的冪的區間覆蓋查詢區間。
維護不具有可減性信息。
靜態區間最大值。
//
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
namespace ST {
int Log2[kMaxn], f[kMaxn][22];
void MakeST () {
Log2[1] = 0;
for (int i = 1; i <= n; ++ i) {
f[i][0] = a[i];
if (i > 1) Log2[i] = Log2[i >> 1] + 1;
}
for (int i = 1; i <= 22; ++ i) {
for (int j = 1; j + (1 << i) - 1 <= n; ++ j) {
f[j][i] = std::max(f[j][i - 1],
f[j + (1 << (i - 1))][i - 1]);
}
}
}
int Query(int l_, int r_) {
int lth = Log2[r_ - l_ + 1];
return std::max(f[l_][lth], f[r_ - (1 << lth) + 1][lth]);
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
ST::MakeST();
while (m --) {
int l = read(), r = read();
printf("%d\n", ST::Query(l, r));
}
return 0;
}
分塊
想了想還是把分塊扔到數據結構里了= =
分塊的基本思想是:通過對原數據的適當划分,並在划分后的每一個塊上預處理部分信息,從而較一般的暴力算法取得更優的時間復雜度。
//知識點:分塊
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
const int kN = 5e4 + 10;
//=============================================================
int n, m;
int block_size, block_num, L[kN], R[kN], bel[kN];
LL a[kN], sum[kN], tag[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmin(LL &fir, LL sec) {
if (sec < fir) fir = sec;
}
void PrepareBlock() {
block_size = sqrt(n); //根據實際要求選擇一個合適的大小。
block_num = n / block_size;
for (int i = 1; i <= block_num; ++ i) { //分配塊左右邊界。
L[i] = (i - 1) * block_size + 1;
R[i] = i * block_size;
}
if (R[block_num] < n) { //最后的一個較小的塊。
++ block_num;
L[block_num] = R[block_num - 1] + 1;
R[block_num] = n;
}
//分配元素所屬的塊編號。
for (int i = 1; i <= block_num; ++ i) {
for (int j = L[i]; j <= R[i]; ++ j) {
bel[j] = i;
sum[i] += a[j]; //預處理區間和
}
}
}
void Modify(int L_, int R_, int val_) {
int bell = bel[L_], belr = bel[R_];
if (bell == belr) {
for (int i = L_; i <= R_; ++ i) {
a[i] += val_;
sum[bell] += val_;
}
return ;
}
for (int i = bell + 1; i <= belr - 1; ++ i) tag[i] += val_;
for (int i = L_; i <= R[bell]; ++ i) {
a[i] += val_;
sum[bell] += val_;
}
for (int i = L[belr]; i <= R_; ++ i) {
a[i] += val_;
sum[belr] += val_;
}
}
LL Query(int L_, int R_, int mod_) {
int bell = bel[L_], belr = bel[R_], ret = 0;
if (bell == belr) {
for (int i = L_; i <= R_; ++ i) ret = (ret + a[i] + tag[bell]) % mod_;
return ret;
}
for (int i = bell + 1; i <= belr - 1; ++ i) {
ret = (ret + sum[i] + (R[i] - L[i] + 1) * tag[i] % mod_) % mod_;
}
for (int i = L_; i <= R[bell]; ++ i) ret = (ret + a[i] + tag[bell]) % mod_;
for (int i = L[belr]; i <= R_; ++ i) ret = (ret + a[i] + tag[belr]) % mod_;
return ret;
}
//=============================================================
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
PrepareBlock();
for (int i = 1; i <= n; ++ i) {
int opt = read(), l = read(), r = read(), c = read();
if (opt == 0) {
Modify(l, r, c);
} else {
printf("%lld\n", Query(l, r, c + 1));
}
}
return 0;
}
線段樹
區間加區間和
//知識點:線段樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
namespace Seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
LL sum[kMaxn << 2], tag[kMaxn << 2];
void Pushup(int now_) {
sum[now_] = sum[ls] + sum[rs];
}
void Pushdown(int now_, int L_, int R_) {
sum[ls] += 1ll * tag[now_] * (mid - L_ + 1);
sum[rs] += 1ll * tag[now_] * (R_ - mid);
tag[ls] += tag[now_];
tag[rs] += tag[now_];
tag[now_] = 0ll;
}
void Build(int now_, int L_, int R_) {
if (L_ == R_) {
sum[now_] = a[L_];
tag[now_] = 0ll;
return ;
}
Build(ls, L_, mid);
Build(rs, mid + 1, R_);
Pushup(now_);
}
void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) {
if (l_ <= L_ and R_ <= r_) {
sum[now_] += 1ll * (R_ - L_ + 1) * val_;
tag[now_] += val_;
return ;
}
Pushdown(now_, L_, R_);
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
Pushup(now_);
}
LL Query(int now_, int L_, int R_, int l_, int r_) {
if (l_ <= L_ and R_ <= r_) return sum[now_];
Pushdown(now_, L_, R_);
LL ret = 0;
if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
return ret;
}
#undef ls
#undef rs
#undef mid
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
Seg::Build(1, 1, n);
while (m --) {
int opt = read();
if (opt == 1) {
int l = read(), r = read(), k = read();
Seg::Modify(1, 1, n, l, r, 1ll * k);
} else {
int l = read(), r = read();
printf("%lld\n", Seg::Query(1, 1, n, l, r));
}
}
return 0;
}
可持久化線段樹
單點修改,單點歷史版本查詢。
//知識點:可持久化線段樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e6 + 10;
//=============================================================
int n, m, a[kMaxn], root[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
namespace Seg {
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
int node_num, lson[kMaxn << 5], rson[kMaxn << 5], val[kMaxn << 5];
void Build(int &now_, int L_, int R_) {
now_ = ++ node_num;
if (L_ == R_) {
val[now_] = a[L_];
return ;
}
Build(ls, L_, mid);
Build(rs, mid + 1, R_);
}
void Modify(int &now_, int pre_, int L_, int R_, int pos_, int val_) {
now_ = ++ node_num;
if (L_ == R_) {
val[now_] = val_;
return ;
}
ls = lson[pre_];
rs = rson[pre_];
if (pos_ <= mid) Modify(ls, lson[pre_], L_, mid, pos_, val_);
else Modify(rs, rson[pre_], mid + 1, R_, pos_, val_);
}
int Query(int now_, int L_, int R_, int pos_) {
if (L_ == R_) return val[now_];
if (pos_ <= mid) return Query(ls, L_, mid, pos_);
return Query(rs, mid + 1, R_, pos_);
}
#undef ls
#undef rs
#undef mid
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
Seg::Build(root[0], 1, n);
for (int i = 1; i <= m; ++ i) {
int pre = read(), opt = read(), pos = read();
if (opt == 1) {
int val = read();
Seg::Modify(root[i], root[pre], 1, n, pos, val);
} else {
printf("%d\n", Seg::Query(root[pre], 1, n, pos));
root[i] = root[pre];
}
}
return 0;
}
動態開點線段樹合並
動態開點 的線段樹的合並。
總復雜度是 \(O(n\log n)\),\(n\) 是節點個數。
P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合並
樹上路徑塗色,修改完后單點查詢最多塗色數。
//知識點:線段樹合並
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define LL long long
const int kMaxn = 3e5 + 10;
const int kInf = 1e5 + 5;
//=============================================================
int n, m, root[kMaxn];
int edge_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int dep[kMaxn], fa[kMaxn], son[kMaxn], top[kMaxn], size[kMaxn];
int ans[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_;
ne[edge_num] = head[u_];
head[u_] = edge_num;
}
namespace Cut {
void Dfs1(int u_, int fat_) {
fa[u_] = fat_;
size[u_] = 1;
dep[u_] = dep[fat_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fat_) continue ;
Dfs1(v_, u_);
size[u_] += size[v_];
if (size[v_] > size[son[u_]]) son[u_] = v_;
}
}
void Dfs2(int u_, int topn_) {
top[u_] = topn_;
if (son[u_]) Dfs2(son[u_], topn_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == son[u_] || v_ == fa[u_]) continue ;
Dfs2(v_, v_);
}
}
int GetLca(int x_, int y_) {
for (; top[x_] != top[y_]; x_ = fa[top[x_]]) {
if (dep[top[x_]] < dep[top[y_]]) std :: swap(x_, y_);
}
return dep[x_] < dep[y_] ? x_ : y_;
}
}
namespace Seg {
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
int node_num, lson[kMaxn << 5], rson[kMaxn << 5], sum[kMaxn << 5];
void Pushup(int now_) {
sum[now_] = std::max(sum[ls], sum[rs]);
}
void Insert(int &now_, int L_, int R_, int pos_, int val_) {
if (! now_) now_ = ++ node_num;
if (L_ == R_) {
sum[now_] += val_;
return ;
}
if (pos_ <= mid) Insert(ls, L_, mid, pos_, val_);
else Insert(rs, mid + 1, R_, pos_, val_);
Pushup(now_);
}
//不新建結點寫法,now 直接連上了 y。
//若之后 now 還和其他的合並,就破壞了 y 的結構。
int Merge(int now_, int y_, int L_, int R_) {
if (! now_ || ! y_) return now_ + y_;
if (L_ == R_) {
sum[now_] += sum[y_];
return now_;
}
ls = Merge(ls, lson[y_], L_, mid);
rs = Merge(rs, rson[y_], mid + 1, R_);
Pushup(now_);
return now_;
}
int Query(int now_, int L_, int R_) {
if (L_ == R_) return L_;
if (sum[ls] >= sum[rs]) return Query(ls, L_, mid);
else return Query(rs, mid + 1, R_);
}
#undef ls
#undef rs
#undef mid
}
void Dfs(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa[u_]) continue ;
Dfs(v_);
root[u_] = Seg::Merge(root[u_], root[v_], 1, kInf);
}
if (! Seg::sum[root[u_]]) ans[u_] = 0;
else ans[u_] = Seg::Query(root[u_], 1, kInf);
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i < n; ++ i) {
int u = read(), v = read();
AddEdge(u, v), AddEdge(v, u);
}
Cut::Dfs1(1, 0), Cut::Dfs2(1, 1);
for (int i = 1; i <= m; ++ i) {
int u = read(), v = read(), z = read();
int lca = Cut::GetLca(u, v);
Seg::Insert(root[u], 1, kInf, z, 1);
Seg::Insert(root[v], 1, kInf, z, 1);
Seg::Insert(root[lca], 1, kInf, z, - 1);
if (lca != 1) Seg::Insert(root[fa[lca]], 1, kInf, z, - 1);
}
Dfs(1);
for (int i = 1; i <= n; ++ i) printf("%d\n", ans[i]);
return 0;
}
主席樹
動態開點權值線段樹。
靜態區間最 k 大。
//知識點:主席樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 2e5 + 10;
//=============================================================
int n, m, d_num, a[kMaxn], data[kMaxn], map[kMaxn];
int root[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Prepare() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) {
a[i] = data[i] = read();
}
std::sort(data + 1, data + n + 1);
data[0] = -0x3f3f3f3f;
for (int i = 1; i <= n; ++ i) {
if (data[i] != data[i - 1]) d_num ++;
data[d_num] = data[i];
}
for (int i = 1; i <= n; ++ i) {
int ori = a[i];
a[i] = std :: lower_bound(data + 1, data + d_num + 1, ori) - data;
map[a[i]] = ori;
}
}
namespace Hjt {
#define ls lson[now_]
#define rs rson[now_]
#define mid ((L_+R_)>>1)
int node_num, lson[kMaxn << 5], rson[kMaxn << 5], size[kMaxn << 5];
void Insert(int &now_, int pre_, int L_, int R_, int val_) {
now_ = ++ node_num;
size[now_] = size[pre_] + 1;
ls = lson[pre_], rs = rson[pre_];
if (L_ == R_) return ;
if (val_ <= mid) Insert(ls, lson[pre_], L_, mid, val_);
else Insert(rs, rson[pre_], mid + 1, R_, val_);
}
int Query(int r_, int l_, int L_, int R_, int k_) {
if (L_ == R_) return L_;
int sz = size[lson[r_]] - size[lson[l_]];
if (k_ <= sz) return Query(lson[r_], lson[l_], L_, mid, k_);
else return Query(rson[r_], rson[l_], mid + 1, R_, k_ - sz);
}
#undef ls
#undef rs
#undef mid
}
//=============================================================
int main() {
Prepare();
for (int i = 1; i <= n; ++ i) {
Hjt::Insert(root[i], root[i - 1], 1, d_num, a[i]);
}
for (int i = 1; i <= m; ++ i) {
int l = read(), r = read(), k = read();
printf("%d\n", map[Hjt::Query(root[r], root[l - 1], 1, d_num, k)]);
}
return 0;
}
帶修主席樹
樹狀數組套主席樹。
//知識點:主席樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
struct Opt {
bool type;
int l, r, k, pos, val;
} q[kN];
int n, m, d_num, data[kN], num[kN];
std::vector <int> tmp1, tmp2;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace Hjt {
#define ls lson[now_]
#define rs rson[now_]
#define mid ((L_+R_)>>1)
int node_num, root[kN], sz[kN << 5], lson[kN << 5], rson[kN << 5];
void Modify(int &now_, int L_, int R_, int pos_, int val_) {
if (!now_) now_ = ++ node_num;
sz[now_] += val_;
if (L_ == R_) return ;
if (pos_ <= mid) Modify(ls, L_, mid, pos_, val_);
else Modify(rs, mid + 1, R_, pos_, val_);
}
int Query(int L_, int R_, int k_) {
if (L_ == R_) return L_;
int sizel = 0;
for (int i = 0; i < tmp1.size(); ++ i) sizel -= sz[lson[tmp1[i]]];
for (int i = 0; i < tmp2.size(); ++ i) sizel += sz[lson[tmp2[i]]];
if (k_ <= sizel) {
for (int i = 0; i < tmp1.size(); ++ i) tmp1[i] = lson[tmp1[i]];
for (int i = 0; i < tmp2.size(); ++ i) tmp2[i] = lson[tmp2[i]];
return Query(L_, mid, k_);
}
for (int i = 0; i < tmp1.size(); ++ i) tmp1[i] = rson[tmp1[i]];
for (int i = 0; i < tmp2.size(); ++ i) tmp2[i] = rson[tmp2[i]];
return Query(mid + 1, R_, k_ - sizel);
}
}
namespace Bit {
#define low(x) (x&-x)
void Add(int pos_, int val_) {
int p = std::lower_bound(data + 1, data + d_num + 1, num[pos_]) - data;
for (int i = pos_; i <= n; i += low(i)) {
Hjt::Modify(Hjt::root[i], 1, d_num, p, val_);
}
}
int Query(int l_, int r_, int k_) {
tmp1.clear(), tmp2.clear();
for (int i = l_ - 1; i; i -= low(i)) tmp1.push_back(Hjt::root[i]);
for (int i = r_; i; i -= low(i)) tmp2.push_back(Hjt::root[i]);
return Hjt::Query(1, d_num, k_);
}
}
void Init() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) data[++ d_num] = num[i] = read();
for (int i = 1; i <= m; ++ i) {
char opt[5]; scanf("%s", opt + 1);
q[i].type = (opt[1] == 'Q');
if (q[i].type) {
q[i].l = read(), q[i].r = read(), q[i].k = read();
} else {
q[i].pos = read(), data[++ d_num] = q[i].val = read();
}
}
std::sort(data + 1, data + d_num + 1);
d_num = std::unique(data + 1, data + d_num + 1) - data - 1;
for (int i = 1; i <= n; ++ i) Bit::Add(i, 1);
}
//=============================================================
int main() {
Init();
for (int i = 1; i <= m; ++ i) {
if (q[i].type) {
int l = q[i].l, r = q[i].r, k = q[i].k;
printf("%d\n", data[Bit::Query(l, r, k)]);
} else {
int pos = q[i].pos, val = q[i].val;
Bit::Add(pos, -1);
num[pos] = val;
Bit::Add(pos, 1);
}
}
return 0;
}
線段樹分治
有這樣的一種模型:
- 給定一些僅在 給定時間范圍 內有效的操作。
- 詢問某個時間點所有操作的貢獻。
考慮離線操作,對時間軸建立線段樹,將每個操作轉化為線段樹上的區間標記操作。
查詢時遍歷整棵線段樹,到達每個節點時執行相應的操作,到達葉節點統計貢獻,回溯時撤銷操作的影響
//知識點:線段樹分治,可撤銷並查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#define pr std::pair
#define mp std::make_pair
#define LL long long
const int kMaxn = 5e4 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
struct Operation {
int type, u, v;
} opt[kMaxm << 1];
struct Stack {
int u, v, fa, size;
} st[kMaxm];
int n, m, k, top, fa[kMaxn], size[kMaxn];
std::map <pr <int, int>, int> tim;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
int Find(int x_) {
while (x_ != fa[x_]) x_ = fa[x_];
return x_;
}
void Union(int u_, int v_) {
int fu = Find(u_), fv = Find(v_);
if (size[fu] > size[fv]) std :: swap(fu, fv);
st[++ top] = (Stack) {fu, fv, fa[fu], size[fu]};
if (fu != fv) {
fa[fu] = fv;
size[fv] += size[fu];
size[fu] = 0;
}
}
void Restore() {
Stack now = st[top];
if (now.u != now.v) {
fa[now.u] = now.fa;
size[now.v] -= now.size;
size[now.u] = now.size;
}
top --;
}
namespace Seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
std::vector <int> t[kMaxm << 2];
void Modify(int now_, int L_, int R_, int l_, int r_, int val_) {
if (l_ <= L_ && R_ <= r_) {
t[now_].push_back(val_);
return ;
}
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
}
void Solve(int now_, int L_, int R_) {
for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
Operation nowo = opt[t[now_][i]];
Union(nowo.u, nowo.v);
}
if (L_ == R_) {
if (opt[L_].type == 2) {
int u = opt[L_].u, v = opt[L_].v;
printf("%c\n", Find(u) == Find(v) ? 'Y' : 'N');
}
for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
Restore();
}
return ;
}
Solve(ls, L_, mid);
Solve(rs, mid + 1, R_);
for (int i = 0, lim = t[now_].size(); i < lim; ++ i) {
Restore();
}
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int nowo = read(), u = read(), v = read();
opt[i] = (Operation) {nowo, u, v};
if (u > v) std::swap(u, v);
if (nowo == 0) {
tim[mp(u, v)] = i;
} else if (nowo == 1) {
Seg::Modify(1, 1, m, tim[mp(u, v)], i - 1, i);
tim[mp(u, v)] = 0;
}
}
for (std::map <pr<int, int>, int>::iterator it = tim.begin(); it != tim.end(); ++ it) {
if (it->second) {
Seg::Modify(1, 1, m, it->second, m, it->second);
}
}
for (int i = 1; i <= n; ++ i) {
fa[i] = i;
size[i] = 1;
}
Seg::Solve(1, 1, m);
return 0;
}
輕重鏈剖分
將一棵樹分為多條重鏈,將樹信息轉化為序列信息。
維護路徑信息時用多條重鏈信息覆蓋路徑。
路徑加,路徑和,子樹加,子樹和,樹上路徑問題轉化為 dfn 序上的序列問題,線段樹維護序列信息。
我八十年沒寫樹剖了居然 1A 了= =
//知識點:重鏈剖分,線段樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, root, p, ori[kMaxn], a[kMaxn];
int e_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int fa[kMaxn], son[kMaxn], dep[kMaxn], size[kMaxn], top[kMaxn];
int d_num, dfn[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
namespace Seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
LL sum[kMaxn << 2], tag[kMaxn << 2];
void Pushup(int now_) {
sum[now_] = (sum[ls] + sum[rs]) % p;
}
void Pushdown(int now_, int L_, int R_) {
sum[ls] += 1ll * tag[now_] * (mid - L_ + 1) % p;
sum[rs] += 1ll * tag[now_] * (R_ - mid) % p;
tag[ls] += tag[now_] % p;
tag[rs] += tag[now_] % p;
sum[ls] %= p, sum[rs] %= p;
tag[ls] %= p, tag[rs] %= p;
tag[now_] = 0ll;
}
void Build(int now_, int L_, int R_) {
if (L_ == R_) {
sum[now_] = a[L_] % p;
tag[now_] = 0ll;
return ;
}
Build(ls, L_, mid);
Build(rs, mid + 1, R_);
Pushup(now_);
}
void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) {
if (l_ <= L_ and R_ <= r_) {
sum[now_] += 1ll * (R_ - L_ + 1) * val_ % p;
tag[now_] += val_ % p;
sum[now_] %= p, tag[now_] %= p;
return ;
}
Pushdown(now_, L_, R_);
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
Pushup(now_);
}
LL Query(int now_, int L_, int R_, int l_, int r_) {
if (l_ <= L_ and R_ <= r_) return sum[now_];
Pushdown(now_, L_, R_);
LL ret = 0;
if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
return ret % p;
}
#undef ls
#undef rs
#undef mid
}
namespace Cut {
void Dfs1(int u_, int fa_) {
fa[u_] = fa_;
size[u_] = 1;
dep[u_] = dep[fa_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
Dfs1(v_, u_);
if (size[v_] > size[son[u_]]) son[u_] = v_;
size[u_] += size[v_];
}
}
void Dfs2(int u_, int top_) {
dfn[u_] = ++ d_num;
a[d_num] = ori[u_];
top[u_] = top_;
if (son[u_]) Dfs2(son[u_], top_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa[u_] or v_ == son[u_]) continue ;
Dfs2(v_, v_);
}
}
void Modify(int u_, int v_, int val_) {
for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
if (dep[top[u_]] < dep[top[v_]]) {
std::swap(u_, v_);
}
Seg::Modify(1, 1, n, dfn[top[u_]], dfn[u_], val_);
}
if (dep[u_] < dep[v_]) std::swap(u_, v_);
Seg::Modify(1, 1, n, dfn[v_], dfn[u_], val_);
}
LL Query(int u_, int v_) {
LL ret = 0;
for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
if (dep[top[u_]] < dep[top[v_]]) {
std::swap(u_, v_);
}
ret += Seg::Query(1, 1, n, dfn[top[u_]], dfn[u_]);
ret %= p;
}
if (dep[u_] < dep[v_]) std::swap(u_, v_);
ret += Seg::Query(1, 1, n, dfn[v_], dfn[u_]);
ret %= p;
return ret;
}
}
//=============================================================
int main() {
n = read(), m = read(), root = read(), p = read();
for (int i = 1; i <= n; ++ i) ori[i] = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_), AddEdge(v_, u_);
}
Cut::Dfs1(root, 0), Cut::Dfs2(root, root);
Seg::Build(1, 1, n);
while (m --) {
int opt = read();
if (opt == 1) {
int u_ = read(), v_ = read(), val_ = read() % p;
Cut::Modify(u_, v_, val_);
} else if (opt == 2) {
int u_ = read(), v_ = read();
printf("%lld\n", Cut::Query(u_, v_));
} else if (opt == 3) {
int x_ = read(), val_ = read() % p;
Seg::Modify(1, 1, n, dfn[x_], dfn[x_] + size[x_] - 1, val_);
} else if (opt == 4) {
int x_ = read();
printf("%lld\n", Seg::Query(1, 1, n, dfn[x_], dfn[x_] + size[x_] - 1));
}
}
return 0;
}
Splay
普通平衡樹
Splay 是一種二叉搜索樹的維護方式。它通過不斷將某節點旋轉到根節點,使得整棵樹仍滿足二叉搜索樹的性質,且保持平衡不至於退化為鏈,以保證復雜度。
//知識點:Splay
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace Splay {
#define f fa[now_]
#define ls son[now_][0]
#define rs son[now_][1]
const int kMaxNode = 1e6 + 10;
int root, node_num, fa[kMaxNode], son[kMaxNode][2];
int val[kMaxNode], cnt[kMaxNode], siz[kMaxNode];
int top, bin[kMaxNode];
void Pushup(int now_) { //更新節點大小信息
if (!now_) return ;
siz[now_] = cnt[now_];
if (ls) siz[now_] += siz[ls];
if (rs) siz[now_] += siz[rs];
}
int WhichSon(int now_) { //獲得兒子類型
return now_ == son[f][1];
}
void Clear(int now_) { //清空節點,同時進行垃圾回收
f = ls = rs = val[now_] = cnt[now_] = siz[now_] = 0;
bin[++ top] = now_;
}
int NewNode(int fa_, int val_) { //建立新節點
int now_ = top ? bin[top --] : ++ node_num; //優先調用垃圾堆
f = fa_, val[now_] = val_, siz[now_] = cnt[now_] = 1;
return now_;
}
void Rotate(int now_) { //旋轉操作
int fa_ = f, whichson = WhichSon(now_);
if (fa[f]) son[fa[f]][WhichSon(f)] = now_;
f = fa[f];
son[fa_][whichson] = son[now_][whichson ^ 1];
fa[son[fa_][whichson]] = fa_;
son[now_][whichson ^ 1] = fa_;
fa[fa_] = now_;
Pushup(fa_), Pushup(now_);
}
void Splay(int now_) { //旋轉到根操作
for (; f != 0; Rotate(now_)) {
if (fa[f]) Rotate(WhichSon(now_) == WhichSon(f) ? f : now_);
}
root = now_;
}
void Insert(int now_, int fa_, int val_) { //插入操作
if (now_ && val[now_] != val_) {
Insert(son[now_][val[now_] < val_], now_, val_);
return ;
}
if (val[now_] == val_) ++ cnt[now_];
if (!now_) {
now_ = NewNode(fa_, val_);
if (f) son[f][val[f] < val_] = now_;
}
Pushup(now_), Pushup(f), Splay(now_); //注意旋轉到根
return ;
}
int Find(int now_, int val_) { //將權值 val 對應節點旋轉至根。在平衡樹上二分。
if (!now_) return false; //不存在
if (val_ < val[now_]) return Find(ls, val_);
if (val_ == val[now_]) {
Splay(now_);
return true;
}
return Find(rs, val_);
}
void Delete(int val_) { //刪除一個權值 val
if (!Find(root, val_)) return ; //將 val 轉到根
if (cnt[root] > 1) {
-- cnt[root];
Pushup(root);
return ;
}
int oldroot = root;
if (!son[root][0] && !son[root][1]) {
root = 0;
} else if (!son[root][0]) {
root = son[root][1], fa[root] = 0;
} else if (!son[root][1]) {
root = son[root][0], fa[root] = 0;
} else if (son[root][0] && son[root][1]) {
//將中序遍歷中 root 前的一個元素作為新的 root。該元素即為 root 左子樹中最大的元素。
int leftmax = son[root][0];
while (son[leftmax][1]) leftmax = son[leftmax][1];
Splay(leftmax); //轉到根
son[root][1] = son[oldroot][1], fa[son[root][1]] = root; //繼承信息
}
Clear(oldroot), Pushup(root);
}
int QueryRank(int val_) { //查詢 val_ 的排名
Insert(root, 0, val_); //先插入,將其轉到根,查詢左子樹大小
int ret = siz[son[root][0]] + 1;
Delete(val_);
return ret;
}
int QueryVal(int rk_) { //查詢排名為 rk 的權值。在平衡樹上二分。
int now_ = root;
while (true) {
if (!now_) return -1;
if (ls && siz[ls] >= rk_) { //注意 =
now_ = ls;
}else {
rk_ -= ls ? siz[ls] : 0;
if (rk_ <= cnt[now_]) { //該權值即為 val[now_]
Splay(now_);
return val[now_];
}
rk_ -= cnt[now_];
now_ = rs;
}
}
}
int QueryPre(int val_) { //查詢前驅
Insert(root, 0, val_); //插入 val_,將其旋轉到根,前驅(中序遍歷中前一個嚴肅)即為左子樹中最大的元素。
int now_ = son[root][0];
while (rs) now_ = rs;
Delete(val_);
return val[now_];
}
int QueryNext(int val_) { //查詢后繼
Insert(root, 0, val_);
int now_ = son[root][1];
while (ls) now_ = ls;
Delete(val_);
return val[now_];
}
}
//=============================================================
int main() {
int n = read();
while (n --) {
int opt = read(), x = read();
if (opt == 1) {
Splay::Insert(Splay::root, 0, x);
} else if (opt == 2) {
Splay::Delete(x);
} else if (opt == 3) {
printf("%d\n", Splay::QueryRank(x));
} else if (opt == 4) {
printf("%d\n", Splay::QueryVal(x));
} else if (opt == 5) {
printf("%d\n", Splay::QueryPre(x));
} else if (opt == 6) {
printf("%d\n", Splay::QueryNext(x));
}
}
return 0;
}
文藝平衡樹
將被操作區間放置到一棵單獨的子樹內,對整棵子樹打標記實現區間修改。注意標記下放的寫法,反轉標記取反並交換左右兒子。
//知識點:Splay
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace Splay {
#define f fa[now_]
#define ls son[now_][0]
#define rs son[now_][1]
const int kMaxNode = kN;
int root, node_num, fa[kMaxNode], son[kMaxNode][2];
int val[kMaxNode], siz[kMaxNode], cnt[kMaxNode];
bool tag[kN];
int top, bin[kMaxNode];
void Pushup(int now_) {
if (!now_) return ;
siz[now_] = cnt[now_];
if (ls) siz[now_] += siz[ls];
if (rs) siz[now_] += siz[rs];
}
void Pushdown(int now_) {
if (!now_ || !tag[now_]) return ;
tag[ls] ^= 1, tag[rs] ^= 1;
std::swap(ls, rs);
tag[now_] = false;
}
void Clear(int now_) {
f = ls = rs = val[now_] = cnt[now_] = siz[now_] = 0;
bin[++ top] = now_;
}
int NewNode(int fa_, int val_) {
int now_ = top ? bin[top --] : ++ node_num;
f = fa_, val[now_] = val_, cnt[now_] = siz[now_] = 1;
return now_;
}
bool WhichSon(int now_) {
return now_ == son[f][1];
}
void Rotate(int now_) {
int fa_ = f, whichson = WhichSon(now_);
if (fa[f]) son[fa[f]][WhichSon(f)] = now_;
f = fa[f];
son[fa_][whichson] = son[now_][whichson ^ 1];
fa[son[fa_][whichson]] = fa_;
son[now_][whichson ^ 1] = fa_;
fa[fa_] = now_;
Pushup(fa_), Pushup(now_);
}
void Splay(int now_, int fa_) {
for (; f != fa_; Rotate(now_)) {
if (fa[f] != fa_) Rotate(WhichSon(f) == WhichSon(now_) ? f : now_);
}
if (!fa_) root = now_;
}
void Build(int &now_, int fa_, int L_, int R_) {
if (L_ > R_) return;
int mid = (L_ + R_) >> 1;
now_ = NewNode(fa_, mid);
Build(ls, now_, L_, mid - 1);
Build(rs, now_, mid + 1, R_);
Pushup(now_);
}
int Kth(int now_, int rk_) {
Pushdown(now_);
if (ls && rk_ <= siz[ls]) return Kth(ls, rk_);
if (rk_ <= siz[ls] + cnt[now_]) return now_;
return Kth(rs, rk_ - siz[ls] - cnt[now_]);
}
void Modify(int L_, int R_) {
int x = Kth(root, L_ - 1 + 1), y = Kth(root, R_ + 1 + 1); //偏移量
Splay(x, 0), Splay(y, x);
tag[son[son[root][1]][0]] ^= 1;
}
void Print(int now_) {
Pushdown(now_);
if (ls) Print(ls);
if (val[now_] && val[now_] <= n) printf("%d ", val[now_]);
if (rs) Print(rs);
}
}
//=============================================================
int main() {
n = read(), m = read();
Splay::Build(Splay::root, 0, 0, n + 1);
while (m --) {
int l = read(), r = read();
Splay::Modify(l ,r);
}
Splay::Print(Splay::root);
return 0;
}
Link-Cut Tree
用於解決動態樹問題,通過 Splay 動態維護樹鏈剖分結構,從而將樹上問題轉化為 Splay 上的序列問題的一種數據結構。
支持動態加刪除邊,修改路徑信息,查詢路徑、子樹信息。不支持修改子樹。
//知識點:LCT
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
const LL mod = 51061;
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace LCT {
#define f fa[now_]
#define ls son[now_][0]
#define rs son[now_][1]
const int kMaxNode = kN;
int fa[kMaxNode], son[kMaxNode][2], val[kMaxNode]; //Splay 的結構信息
LL siz[kMaxNode], sum[kMaxNode], tagplus[kMaxNode], tagprod[kMaxNode]; //子樹大小,子樹和,兩種標記
bool tagrev[kMaxNode]; //Splay 的子樹反轉標記
void Pushup(int now_) { //維護子樹和 和 子樹大小
sum[now_] = (sum[ls] + sum[rs] + val[now_]) % mod;
siz[now_] = siz[ls] + siz[rs] + 1;
}
void PushReverse(int now_) { //子樹反轉標記,使得 Splay 節點的中序遍歷反向。若原 splay 表示一條自頂向下的鏈,反轉相當於將 splay 表示的鏈的邊反向,父子關系互換。
if (!now_) return;
std::swap(ls, rs);
tagrev[now_] ^= 1;
}
void PushPlus(int now_, LL val_) {
if (!now_) return;
val[now_] = (val[now_] + val_) % mod;
sum[now_] = (sum[now_] + siz[now_] * val_ % mod) % mod;
tagplus[now_] = (tagplus[now_] + val_) % mod;
}
void PushProd(int now_, LL val_) {
if (!now_) return;
val[now_] = val[now_] * val_ % mod;
sum[now_] = sum[now_] * val_ % mod;
tagplus[now_] = tagplus[now_] * val_ % mod;
tagprod[now_] = tagprod[now_] * val_ % mod;
}
void Pushdown(int now_) { //注意下放順序
LL plus = tagplus[now_], prod = tagprod[now_], rev = tagrev[now_];
if (prod != 1) PushProd(ls, prod), PushProd(rs, prod);
if (plus) PushPlus(ls, plus), PushPlus(rs, plus);
if (rev) PushReverse(ls), PushReverse(rs);
tagprod[now_] = 1, tagplus[now_] = 0, tagrev[now_] = 0;
}
bool IsRoot(int now_) { //判斷 now_ 是否為當前 Splay 的根
return son[f][0] != now_ && son[f][1] != now_;
}
bool WhichSon(int now_) {
return son[f][1] == now_;
}
void Rotate(int now_) {
int fa_ = f, w = WhichSon(now_);
if (!IsRoot(f)) son[fa[f]][WhichSon(f)] = now_;
f = fa[f];
son[fa_][w] = son[now_][w ^ 1];
fa[son[fa_][w]] = fa_;
son[now_][w ^ 1] = fa_;
fa[fa_] = now_;
Pushup(fa_), Pushup(now_);
}
void Update(int now_) { //將 Splay 路徑上的所有標記下放
if (!IsRoot(now_)) Update(f);
Pushdown(now_);
}
void Splay(int now_) {
Update(now_);
for (; !IsRoot(now_); Rotate(now_)) {
if (!IsRoot(f)) Rotate(WhichSon(f) == WhichSon(now_) ? f : now_);
}
}
void Access(int now_) { //使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay,滿足 Splay 的中序遍歷深度遞減的性質。
//自下向上構建,舍棄父親的原有右兒子,換成 -> last 的鏈。
for (int last_ = 0; now_; last_ = now_, now_ = f) {
Splay(now_), rs = last_;
Pushup(now_);
}
}
void MakeRoot(int now_) { //使 now 成為原樹的根
Access(now_); //先使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay。
Splay(now_); //使 now 成為 splay 的根節點
PushReverse(now_); //將根->now 的鏈反轉,使 now 成為原樹的根。原理參考 PushReverse 函數的注釋。
}
int Find(int now_) { //找到 now_ 所在原樹的根
Access(now_);
Splay(now_);
while (ls) Pushdown(now_), now_ = ls; //使得樹中由根->now 的鏈成為實鏈,構造出由它們組成的 Splay,再找到 Splay 中序遍歷的第一個元素,即為原樹的根。
Splay(now_); //為了下一步操作,把根再轉回去
return now_;
}
void Split(int x_, int y_) { //構造由路徑 x->y 組成的 Splay
MakeRoot(x_); //使 x 成為根,構造出根 -> y 的 Splay 即得,Splay 根的子樹信息即為路徑信息。
Access(y_);
Splay(y_);
}
void Link(int x_, int y_) { //加邊 (x,y)
MakeRoot(x_); //使 x 成為根,再給根一個父親
if (Find(y_) != x_) fa[x_] = y_;
}
void Cut(int x_, int y_) { //刪邊 (x,y)
MakeRoot(x_); //使 x 成為根
//Find(y_) != x_ 保證 y 與 x 連通
//在 Find 函數中 Access(y) 之后,y 所在的 splay 由 x->y 的鏈構成。又 x 是 splay 的根,y 的中序遍歷在 x 后,則若邊 (x,y) 存在,則 y 的位置只有一種可能:
//y 是 x 的右兒子,且 y 沒有左兒子。從而保證 y 在中序遍歷中是 x 的后一個。
if (Find(y_) != x_ || fa[y_] != x_ || son[y_][0]) return ;
fa[y_] = son[x_][1] = 0; //斷絕父子關系
Pushup(x_);
}
void Modify(int x_, int y_, int val_, int type) { //路徑修改
Split(x_, y_); //給路徑構成的 Splay 打標記
if (!type) PushPlus(y_, val_);
else PushProd(y_, val_);
}
LL Query(int x_, int y_) { //路徑查詢
Split(x_, y_); //返回路徑構成的 Splay 的信息
return sum[y_];
}
}
//=============================================================
signed main() {
int n = read(), m = read();
for (int i = 1; i <= n; ++ i) {
LCT::val[i] = LCT::tagprod[i] = LCT::siz[i] = 1;
}
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
LCT::Link(u_, v_);
}
for(int i = 1; i <= m; i ++) {
char opt[2]; scanf("%s", opt);
int u1 = read(), v1 = read();
if(opt[0] == '+') LCT::Modify(u1, v1, read(), 0);
if(opt[0] == '*') LCT::Modify(u1, v1, read(), 1);
if(opt[0] == '/') printf("%lld\n", LCT::Query(u1, v1));
if(opt[0] == '-') {
int u2 = read(), v2 = read();
LCT::Cut(u1, v1), LCT::Link(u2, v2);
}
}
return 0;
圖論
多源最短路
Floyd
//知識點:多源最短路
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e3 + 10;
//=============================================================
int n, m, s, f[kMaxn][kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Floyd() {
for (int i = 1; i <= n; ++ i) f[i][i] = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
for (int k = 1; k <= n; ++ k) {
Chkmin(f[j][k], f[j][i] + f[i][k]);
}
}
}
}
//=============================================================
int main() {
n = read(), m = read(), s = read();
memset(f, 63, sizeof (f));
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
Chkmin(f[u_][v_], w_);
}
Floyd();
for (int i = 1; i <= n; ++ i) {
printf("%d ", f[s][i]);
// printf("%d ", f[s][i] >= 0x3f3f3f3f ? 2147483647 : f[s][i]);
}
return 0;
}
單源最短路三合一
Dijkstra,Bellman-Ford,Spfa 三合一代碼。
//知識點:單源最短路
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m, s;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Dijkstra(int s_) {
std::priority_queue <pr <int, int> > q;
memset(dis, 63, sizeof (dis));
memset(vis, 0, sizeof (vis));
dis[s_] = 0;
q.push(mp(0, s_));
while (! q.empty()) {
int u_ = q.top().second;
q.pop();
if (vis[u_]) continue ;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
q.push(mp(-dis[v_], v_));
}
}
}
}
void BellmanFord(int s_) {
memset(dis, 63, sizeof (dis));
dis[s_] = 0;
while (1) {
bool flag = true;
for (int u_ = 1; u_ <= n; ++ u_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
flag = false;
}
}
}
if (flag) break ;
}
}
void Spfa(int s_) {
std::queue <int> q;
memset(vis, 0, sizeof (vis));
memset(dis, 63, sizeof (dis));
q.push(s_);
dis[s_] = 0;
vis[s_] = true;
while (! q.empty()) {
int u_ = q.front();
q.pop();
vis[u_] = false;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
if (! vis[v_]) {
q.push(v_);
vis[v_] = true;
}
}
}
}
}
//=============================================================
int main() {
n = read(), m = read(), s = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
}
Dijkstra(s);
// BellmanFord(s);
// Spfa(s);
for (int i = 1; i <= n; ++ i) {
printf("%d ", dis[i]);
// printf("%d ", dis[i] >= 0x3f3f3f3f ? 2147483647 : dis[i]);
}
return 0;
}
最小生成樹
Kruscal
克魯斯卡爾大戰克蘇魯。
基於排序與並查集的貪心加邊。
//知識點:最小生成樹
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 5e4 + 10;
const int kMaxm = 4e5 + 10;
//=============================================================
int n, m, ans, fa[kMaxn], size[kMaxn];
struct Edge {
int u, v, w;
} e[kMaxm];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
bool CompareEdge(Edge f_, Edge s_) {
return f_.w < s_.w;
}
int Find(int x_) {
return x_ == fa[x_] ? x_ : fa[x_] = Find(fa[x_]);
}
void Union(int u_, int v_) {
int fu = Find(u_), fv = Find(v_);
if (fu == fv) return ;
if (size[fu] > size[fv]) std::swap(fu, fv);
fa[fu] = fv;
size[fv] += size[fu];
}
void Kruscal() {
int tot = 0;
std::sort(e + 1, e + m + 1, CompareEdge);
for (int i = 1; i <= n; ++ i) {
fa[i] = i;
size[i] = 1;
}
for (int i = 1; i <= m && tot < n - 1; ++ i) {
int u_ = e[i].u, v_ = e[i].v, w_ = e[i].w;
if (Find(u_) == Find(v_)) continue ;
Union(u_, v_);
ans += w_;
++ tot;
}
if (tot < n - 1) ans = -1;
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
e[i] = (Edge) {u_, v_, w_};
}
Kruscal();
if (ans < 0) {
printf("orz\n");
} else {
printf("%d\n", ans);
}
return 0;
}
Prim
類似 Dijkstra,貪心地找將每一個點加入連通塊的邊的最小值。
用了堆優化。
//知識點:單元最短路
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 5e4 + 10;
const int kMaxm = 4e5 + 10;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Prim() {
std::priority_queue <pr <int, int> > q;
memset(dis, 63, sizeof (dis));
memset(vis, 0, sizeof (vis));
dis[1] = 0;
q.push(mp(0, 1));
int tot = 0;
while (! q.empty() && tot < n) {
int u_ = q.top().second;
q.pop();
if (vis[u_]) continue ;
++ tot;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (w_ < dis[v_] && !vis[v_]) {
dis[v_] = w_;
q.push(mp(-dis[v_], v_));
}
}
}
if (tot < n) ans = -1;
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
AddEdge(v_, u_, w_);
}
Prim();
if (ans < 0) {
printf("orz\n");
} else {
for (int i = 1; i <= n; ++ i) ans += dis[i];
printf("%d\n", ans);
}
return 0;
}
最近公共祖先
倍增
優化暴力上跳,用2 的冪的和拼出跳的深度。
//知識點:最近公共祖先
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 5e5 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, root;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int dep[kMaxn], fa[kMaxn][21];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Dfs(int u_, int fa_) {
fa[u_][0] = fa_;
dep[u_] = dep[fa_] + 1;
for (int i = 1; i <= 20; ++ i) {
fa[u_][i] = fa[fa[u_][i - 1]][i - 1];
}
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
Dfs(v_, u_);
}
}
int Lca(int u_, int v_) {
if (dep[u_] < dep[v_]) std::swap(u_, v_);
for (int i = 20; i >= 0; -- i) {
if (dep[fa[u_][i]] >= dep[v_] ) {
u_ = fa[u_][i];
}
}
if (u_ == v_) return u_;
for (int i = 20; i >= 0; -- i) {
if (fa[u_][i] != fa[v_][i]) {
u_ = fa[u_][i];
v_ = fa[v_][i];
}
}
return fa[u_][0];
}
//=============================================================
int main() {
n = read(), m = read(), root = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_);
AddEdge(v_, u_);
}
Dfs(root, 0);
while (m --) {
int u_ = read(), v_ = read();
printf("%d\n", Lca(u_, v_));
}
return 0;
}
重鏈剖分
暴力上跳轉化為跳重鏈。
//知識點:重鏈剖分
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 5e5 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, root;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int fa[kMaxn], son[kMaxn], dep[kMaxn], size[kMaxn], top[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
namespace Cut {
void Dfs1(int u_, int fa_) {
fa[u_] = fa_;
size[u_] = 1;
dep[u_] = dep[fa_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
Dfs1(v_, u_);
if (size[v_] > size[son[u_]]) son[u_] = v_;
size[u_] += size[v_];
}
}
void Dfs2(int u_, int top_) {
top[u_] = top_;
if (son[u_]) Dfs2(son[u_], top_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa[u_] or v_ == son[u_]) continue ;
Dfs2(v_, v_);
}
}
int Lca(int u_, int v_) {
for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
if (dep[top[u_]] < dep[top[v_]]) {
std::swap(u_, v_);
}
}
return dep[u_] < dep[v_] ? u_ : v_;
}
}
//=============================================================
int main() {
n = read(), m = read(), root = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_), AddEdge(v_, u_);
}
Cut::Dfs1(root, 0), Cut::Dfs2(root, root);
while (m --) {
int u_ = read(), v_ = read();
printf("%d\n", Cut::Lca(u_, v_));
}
return 0;
}
RMQ
dfs 維護一個序列。
每當一個點被訪問到,就將它加入到序列的尾部。
維護每個節點的深度,在序列中第一次出現的位置。
根據 dfs 的性質,對於節點 x、y,它們第一次出現位置這一段區間內深度最淺的點即為其 LCA。
預處理時間空間復雜度實際上是 \(𝑂(2𝑛 \log(2𝑛))\) 的,單次查詢復雜度 \(O(1)\)。
使用局限性比較大,在某些題目中會有作用。
//知識點:LCA
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e5 + 10;
const int kM = kN << 1;
//=============================================================
int n, m, root;
int e_num, head[kN], v[kM], ne[kM], dep[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
namespace ST {
int num, Log2[kN << 1], f[kN << 1][22], fir[kN];
void Dfs(int u_, int fa_) {
dep[u_] = dep[fa_] + 1;
fir[u_] = ++ num;
f[num][0] = u_;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
Dfs(v_, u_);
f[++ num][0] = u_;
}
}
void Prepare() {
Dfs(root, root);
Log2[1] = 0;
for (int i = 2; i <= num; ++ i) {
Log2[i] = Log2[i >> 1] + 1;
}
for (int i = 1; i <= 21; ++ i) {
for (int j = 1; j + (1 << i) - 1 <= num; ++ j) {
if (dep[f[j][i - 1]] < dep[f[j + (1 << (i - 1))][i - 1]]) {
f[j][i] = f[j][i - 1];
} else {
f[j][i] = f[j + (1 << (i - 1))][i - 1];
}
}
}
}
int Lca(int u_, int v_) {
int l = fir[u_], r = fir[v_];
if (l > r) std::swap(l, r);
int lth = Log2[r - l + 1];
if (dep[f[l][lth]] < dep[f[r - (1 << lth) + 1][lth]]) {
return f[l][lth];
}
return f[r - (1 << lth) + 1][lth];
}
}
//=============================================================
int main() {
n = read(), m = read(), root = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_), AddEdge(v_, u_);
}
ST::Prepare();
for (int i = 1; i <= n; ++ i) {
int u_ = read(), v_ = read();
printf("%d\n", ST::Lca(u_, v_));
}
return 0;
}
樹的直徑
圖中所有最短路徑的最大值即為「直徑」,可以用兩次 DFS 或者樹形 DP 的方法在 O(n) 時間求出樹的直徑。
之前寫的垃圾文章:「筆記」樹的直徑。
樹形 DP
對每個節點維護最長鏈和次長鏈,相加取最大值。
//知識點:樹的直徑
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
int Dfs(int u_, int fa_) {
int f1 = 0, f2 = 0;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
int f = Dfs(v_, u_) + 1;
if (f > f1) {
f2 = f1;
f1 = f;
} else if (f > f2) {
f2 = f;
}
}
Chkmax(ans, f1 + f2);
return f1;
}
//=============================================================
int main() {
n = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_);
AddEdge(v_, u_);
}
Dfs(1, 0);
printf("%d\n", ans);
}
2 次DFS
距離一個點最遠的點一定是直徑的一個端點。
一次 DfS 找到一個端點,第二次找到另一個。
由於 dis 初值是 0,所以需要 -1。
//知識點:樹的直徑
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = kMaxn << 1;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int dis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
int Dfs(int u_, int fa_) {
dis[u_] = dis[fa_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue ;
Dfs(v_, u_);
}
}
//=============================================================
int main() {
n = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_);
AddEdge(v_, u_);
}
Dfs(1, 0);
int max_dis = 0, u_ = 0;
for (int i = 1; i <= n; ++ i) {
if (dis[i] > max_dis) {
max_dis = dis[i];
u_ = i;
}
dis[i] = 0;
}
Dfs(u_, 0);
max_dis = 0, u_ = 0;
for (int i = 1; i <= n; ++ i) {
if (dis[i] > max_dis) {
max_dis = dis[i];
u_ = i;
}
dis[i] = 0;
}
printf("%d\n", max_dis - 1);
}
判斷負權環
Spfa,判斷到達某點最短路上的點數是否 \(>n\)。
若為真則有負環。
//知識點:負環
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn], cnt[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Init() {
e_num = 0;
memset(head, 0, sizeof (head));
}
bool SpfaBfs(int s_) {
std::queue <int> q;
memset(cnt, 0, sizeof (cnt));
memset(vis, 0, sizeof (vis));
memset(dis, 63, sizeof (dis));
q.push(s_);
dis[s_] = 0;
vis[s_] = true;
while (! q.empty()) {
int u_ = q.front();
q.pop();
vis[u_] = false;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
cnt[v_] = cnt[u_] + 1;
if (cnt[v_] > n) return true;
if (! vis[v_]) {
q.push(v_);
vis[v_] = true;
}
}
}
}
return false;
}
//=============================================================
int main() {
int T = read();
while (T --) {
Init();
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
if (w_ >= 0) AddEdge(v_, u_, w_);
}
printf("%s\n", SpfaBfs(1) ? "YES" : "NO");
}
return 0;
}
差分約束
有 \(x_i -x_j\le k\) 的一堆不等式。
轉化為單源最短路中三角不等式 \(dis_i\le dis_j + k\) 的形式,從 \(j\) 向 \(i\) 連一條權值為 \(k\) 的單向邊。
建立超級源點,與各點距離為 0,跑最短路。
若有負環則無解,否則最短路即為一組合法解。
合法解同加 \(\Delta\) 仍為合法解。
//知識點:負環
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 5e5 + 10;
//=============================================================
int n, m;
int e_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
int dis[kMaxn], cnt[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Init() {
e_num = 0;
memset(head, 0, sizeof (head));
}
bool SpfaBfs(int s_) {
std::queue <int> q;
memset(cnt, 0, sizeof (cnt));
memset(vis, 0, sizeof (vis));
memset(dis, 63, sizeof (dis));
q.push(s_);
dis[s_] = 0;
vis[s_] = true;
while (! q.empty()) {
int u_ = q.front();
q.pop();
vis[u_] = false;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
cnt[v_] = cnt[u_] + 1;
if (cnt[v_] > n) return true;
if (! vis[v_]) {
q.push(v_);
vis[v_] = true;
}
}
}
}
return false;
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int v_ = read(), u_ = read(), w_ = read();
AddEdge(u_, v_, w_);
}
for (int i = 1; i <= n; ++ i) {
AddEdge(0, i, 0);
}
if (SpfaBfs(0)) {
printf("NO\n");
return 0;
}
for (int i = 1; i <= n; ++ i) {
printf("%d ", dis[i]);
}
return 0;
}
強連通相關
縮點
縮點 + Topsort DP。
//知識點:縮點
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#define LL long long
const int kMaxn = 1e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, ans, ori[kMaxn];
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int d_num, b_num, dfn[kMaxn], low[kMaxn], bel[kMaxn], val[kMaxn];
int into[kMaxn], f[kMaxn];
std::vector <int> newv[kMaxn];
std::stack <int> st;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Tarjan(int u_) {
dfn[u_] = low[u_] = ++ d_num;
st.push(u_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (! dfn[v_]) {
Tarjan(v_);
Chkmin(low[u_], low[v_]);
} else if (! bel[v_]) {
Chkmin(low[u_], dfn[v_]);
}
}
if (dfn[u_] == low[u_]) { //
++ b_num;
for (int now = 0; now != u_; st.pop()) {
now = st.top();
bel[now] = b_num;
val[b_num] += ori[now];
}
}
}
void Topsort() {
std::queue <int> q;
for (int i = 1; i <= b_num; ++ i) {
if (! into[i]) {
f[i] = val[i];
q.push(i);
}
}
while (! q.empty()) {
int u_ = q.front();
q.pop();
for (int i = 0, lim = newv[u_].size(); i < lim; ++ i) {
int v_ = newv[u_][i];
Chkmax(f[v_], f[u_] + val[v_]);
into[v_] --;
if (! into[v_]) {
q.push(v_);
}
}
}
for (int i = 1; i <= b_num; ++ i) {
Chkmax(ans, f[i]);
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++ i) ori[i] = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read();
if (u_ == v_) continue ;
AddEdge(u_, v_);
}
for (int i = 1; i <= n; ++ i) {
if (! dfn[i]) Tarjan(i);
}
for (int u_ = 1; u_ <= n; ++ u_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (bel[u_] == bel[v_]) continue ;
newv[bel[u_]].push_back(bel[v_]);
into[bel[v_]] ++;
}
}
Topsort();
printf("%d\n", ans);
return 0;
}
割點
以下是幾個月前寫的垃圾文字:
應用Tarjan求割點。
按照dfs的遍歷順序,將圖看成一棵樹,圖便有了層次性,將dfs回溯的邊當做回邊。
dfn[]
記錄每一個點的dfs序,low[]
記錄能連通的dfs序最小的點的編號。
初始時dfn[i] = low[i]
,在不斷dfs遞歸過程中進行更新。對於搜索樹的根節點,若它有兩個 或 兩個以上的子樹,去掉此點后, 其各子樹便不連通,說明它是一個割點。
對於其他的節點,更新完成后, 若有一條邊, 滿足
low[v] >= dfn[u]
。
說明 v 點, 一定到達不了 u 的祖先 (dfs 樹意義下的祖先),則刪去此點后,u 與 v 不連通,u 是一個割點。
//知識點:割點
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#define LL long long
const int kMaxn = 2e4 + 10;
const int kMaxm = 2e5 + 10;
//=============================================================
int n, m, ans;
int e_num, head[kMaxn], v[kMaxm], ne[kMaxm];
int d_num, dfn[kMaxn], low[kMaxn];
bool cut[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Tarjan(int u_, int root_) {
int son = 0;
dfn[u_] = low[u_] = ++ d_num;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (! dfn[v_]) {
Tarjan(v_, root_);
Chkmin(low[u_], low[v_]);
if (u_ == root_) ++ son;
if (low[v_] >= dfn[u_] && u_ != root_) {
ans += (! cut[u_]);
cut[u_] = true;
}
} else {
Chkmin(low[u_], dfn[v_]);
}
}
if (u_ == root_ && son >= 2) {
ans += (!cut[u_]);
cut[u_] = true;
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read();
AddEdge(u_, v_);
AddEdge(v_, u_);
}
for (int i = 1; i <= n; ++ i) {
if (! dfn[i]) {
Tarjan(i, i);
}
}
printf("%d\n", ans);
for (int i = 1; i <= n; ++ i) {
if (cut[i]) {
printf("%d ", i);
}
}
return 0;
}
2-SAT
有 \(n\) 個 01 變量 \(x_1\sim x_n\),另有 \(m\) 個變量取值需要滿足的限制。
每個限制是一個 \(k\) 元組 \((x_{p_1}, x_{p_2}, \cdots, x_{p_3})\),滿足 \(x_{p_1}\oplus x_{p_2}\oplus\cdots\oplus x_{p_3} = a\)。其中 \(a\) 是 \(0/1\),\(\oplus\) 是某種二元 bool 運算。
要求構造一種滿足所有限制的變量的賦值方案。
建立圖論模型,使用強連通算法實現。
//知識點:2-SAT
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 2e6 + 10;
//=============================================================
int n, m, e_num, head[kN], v[kN << 1], ne[kN << 1];
int dfn_num, bel_num, dfn[kN], low[kN], bel[kN];
std::stack <int> st;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Add(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Tarjan(int u_) {
dfn[u_] = low[u_] = ++ dfn_num;
st.push(u_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (!dfn[v_]) {
Tarjan(v_);
Chkmin(low[u_], low[v_]);
} else if (!bel[v_]) {
Chkmin(low[u_], dfn[v_]);
}
}
if (dfn[u_] == low[u_]) {
++ bel_num;
while (true) {
int top = st.top(); st.pop();
bel[top] = bel_num;
if (top == u_) break;
}
}
}
//=============================================================
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++ i) {
int x = read(), a = read(), y = read(), b = read();
if (a && b) Add(x + n, y), Add(y + n, x);
if (!a && b) Add(x, y), Add(y + n, x + n);
if (a && !b) Add(x + n, y + n), Add(y, x);
if (!a && !b) Add(x, y + n), Add(y, x + n);
}
for (int i = 1; i <= 2 * n; ++ i) {
if (!dfn[i]) Tarjan(i);
if (i <= n && bel[i] == bel[i + n]) {
printf("IMPOSSIBLE\n");
return 0;
}
}
printf("POSSIBLE\n");
for (int i = 1; i <= n; ++ i) printf("%d ", bel[i] < bel[i + n]); //Tarjan 算法賦給強連通分量的編號順序與拓撲序相反
return 0;
}
虛樹
對於樹 \(T=(V,E)\),給定關鍵點集 \(S\subseteq V\),則可定義虛樹 \(T'=(V',E')\)。
對於點集 \(V'\subseteq V\),使得 \(u\in V'\) 當且僅當 \(u\in S\),或 \(\exist x,y\in S,\operatorname{lca}(x,y)=u\)。
對於邊集,\((u,v)\in E'\),當且僅當 \(u,v\in V'\),且 \(u\) 為 \(v\) 在 \(V'\) 中深度最深的祖先。
個人理解:
僅保留關鍵點及其 \(\operatorname{lca}\),縮子樹成邊,僅保留分叉點,可能刪去一些不包含關鍵點的子樹。
壓縮了樹的信息,同時也丟失了部分樹的信息。
一個分叉點會合並至少兩個關鍵點,虛樹節點數最多為 \(2k-1\) 個。節點數變為了 \(O(k)\) 級別。
建樹考慮增量法,每次向虛樹中添加一個關鍵點。考慮先求得 關鍵節點 的 dfs 序,按照 dfs 序添加關鍵節點。這樣可以保證相鄰兩個關鍵點的 \(\operatorname{lca}\) 深度不小於不相鄰關鍵點的深度。
考慮單調棧維護虛樹最右側的鏈(上一個關鍵點與根的鏈),單調棧中節點深度遞增,棧頂一定為上一個關鍵點。欽定 1 號節點為根,先將其壓入棧中。
每加入一個關鍵點 \(a_i\),令 \(\operatorname{lca}(a_{i-1},a_i)=w\)。將棧頂 \(\operatorname{dep}_x > \operatorname{dep}_w\) 的彈棧,加入 \(w,a_i\),即為新的右鏈。特別地,若棧頂存在 \(\operatorname{dep}_x=\operatorname{dep}_w\),不加入 \(w\) 節點。
在此過程中維護每個節點的父節點,在彈棧時進行連邊並維護信息,即得虛樹。單次建虛樹復雜度 \(O(kw)\) 級別,其中 \(w\) 為單次求 \(\operatorname{lca}\) 的復雜度。
其中 \(\operatorname{Cut}\) 為封裝后的樹鏈剖分。
虛樹上 DP 板題:「SDOI2011」消耗戰
namespace VT { //Virtual Tree
#define dep Cut::dep
const int kMaxNode = kN;
int top, node[kMaxNode], st[kMaxNode]; //棧
int tag[kMaxNode]; //標記是否為關鍵點
std::vector <int> newv[kMaxNode]; //虛樹
bool CMP(int fir_, int sec_) { //按 dfs 序比較
return Cut::dfn[fir_] < Cut::dfn[sec_];
}
void Push(int u_) { //向虛樹中加入 u_
int lca = Cut::Lca(u_, st[top]);
//為什么這里要寫成 top - 1?因為 lca 可能出現在棧中,這樣寫便於處理。
for (; dep[st[top - 1]] > dep[lca]; -- top) {
newv[st[top - 1]].push_back(st[top]);
}
if (lca != st[top]) {
newv[lca].push_back(st[top]); -- top;
if (lca != st[top]) st[++ top] = lca;
}
if (st[top] != u_) st[++ top] = u_;
}
void Build(int siz_) {
for (int i = 1; i <= siz_; ++ i) {
node[i] = read();
tag[node[i]] = 1;
}
std::sort(node + 1, node + siz_ + 1, CMP);
st[top = 0] = 1;
for (int i = 1; i <= siz_; ++ i) Push(node[i]);
for (; top; -- top) newv[st[top - 1]].push_back(st[top]);
}
}
網絡流
網絡最大流
EK
從源點開始 bfs 實現 FF,到達匯點后停止 bfs 並增廣一次。增廣時記錄轉移前驅並更新路徑上邊的殘余容量,答案加上最小流量。
//知識點:網絡最大流,EK
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 210 + 10;
const int kM = 1e4 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, m, s, t;
int e_num = 1, head[kN], v[kM], ne[kM], from[kN];
LL f[kN], w[kM];
bool vis[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = 1ll * w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
bool Bfs() { //找到一條增廣路
memset(vis, false, sizeof (vis));
std::queue <int> q;
vis[s] = false;
f[s] = kInf;
q.push(s);
while (!q.empty()) {
int u_ = q.front(); q.pop();
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
LL w_ = w[i];
if (!w_ || vis[v_]) continue;
f[v_] = std::min(f[u_], w_);
vis[v_] = true;
from[v_] = i;
q.push(v_);
if (v_ == t) return true;
}
}
return false;
}
LL EK() {
LL ret = 0;
while (Bfs()) { //更改路徑的殘余容量
for (int u_ = t; u_ != s; u_ = v[from[u_] ^ 1]) {
w[from[u_]] -= f[t];
w[from[u_] ^ 1] += f[t];
}
ret += f[t];
}
return ret;
}
//=============================================================
int main() {
n = read(), m = read(), s = read(), t = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
AddEdge(v_, u_, 0);
}
printf("%lld\n", EK());
return 0;
}
Dinic
使用 dfs 找增廣路,不過在每輪增廣前使用 bfs 將殘余網絡分層。一個點的層數為殘余網絡上它與源點的最小邊數。分層時若匯點的層數不存在,說明不存在增廣路,即可停止增廣。
之后 dfs 增廣時,每次轉移僅向比當前點層數多 1 的點轉移。這可以保證每次找到的增廣路是最短的。
多路增廣 + 當前弧優化。
//知識點:網絡最大流,Dinic
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 210 + 10;
const int kM = 1e4 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, m, s, t;
int e_num = 1, head[kN], v[kM], ne[kM];
int cur[kN], dep[kN];
LL w[kM];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_;
w[e_num] = 1ll * w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
bool Bfs() {
std::queue <int> q;
memset(dep, 0, sizeof (dep));
dep[s] = 1; //注意初始化
q.push(s);
while (!q.empty()) {
int u_ = q.front(); q.pop();
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
LL w_ = w[i];
if (w_ > 0 && !dep[v_]) {
dep[v_] = dep[u_] + 1;
q.push(v_);
}
}
}
return dep[t];
}
LL Dfs(int u_, LL into_) {
if (u_ == t || !into_) return into_;
LL out = 0;
for (int& i = cur[u_]; i && into_; i = ne[i]) { //當前弧優化
int v_ = v[i];
LL w_ = w[i];
if (dep[v_] == dep[u_] + 1 && w_ > 0) {
LL ret = Dfs(v_, std::min(into_, w_));
if (!ret) dep[v_] = kN; //沒有貢獻,之后不會再訪問
w[i] -= ret, w[i ^ 1] += ret;
out += ret, into_ -= ret;
}
}
return out;
}
LL Dinic() {
LL ret = 0;
while (Bfs()) {
for (int i = 1; i <= n; ++ i) cur[i] = head[i]; //初始化當前弧
ret += Dfs(s, kInf);
}
return ret;
}
//=============================================================
int main() {
n = read(), m = read(), s = read(), t = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_);
AddEdge(v_, u_, 0);
}
printf("%lld\n", Dinic());
return 0;
}
二分圖匹配
匈牙利
枚舉一邊的點,找增廣路。
復雜度 \(O(n^2 m)\)。
//知識點:二分圖最大匹配
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 500 + 10;
const int kMaxe = 5e4 + 10;
//=============================================================
int n, m, e, ans;
int e_num, head[kMaxn], v[kMaxe], ne[kMaxe];
int match[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
bool Dfs(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (vis[v_]) continue ;
vis[v_] = true;
if (! match[v_] or Dfs(match[v_])) {
match[v_] = u_;
return true;
}
}
return false;
}
//=============================================================
int main() {
n = read(), m = read(), e = read();
for (int i = 1; i <= e; ++ i) {
int u_ = read(), v_ = read();
if (u_ > n || v_ > m) continue ;
AddEdge(u_, v_);
}
for (int i = 1; i <= n; ++ i) {
memset(vis, 0, sizeof (vis));
if (Dfs(i)) ++ ans;
}
printf("%d\n", ans);
return 0;
}
網絡流
建模,在一個匹配中, 任意兩條邊都沒有共同的端點,每一個端點 最多只有 1 的貢獻。 在網絡流中,最多只有 1 的貢獻可轉化為 邊的容量為 1。由此可對原二分圖進行轉化:
新建源點 與 匯點。
- 源點向每個左側點 各連一條容量為 1 的有向邊。
- 原圖中的邊改為 自左向右的有向邊,容量為 1。
- 每個右側點向匯點 各連一條容量為 1 的有向邊。
求最大流即為最大匹配。
線段樹優化建圖
用於解決區間連邊的一個建圖小技巧。
對於區間連邊問題,其解決方案是建立一系列虛點,虛點到實點的權值為 0,一個虛點與某一段連續區間內的實點相連。此時若想對該區間進行區間連邊,直接連向此虛點即可。
為解決區間的拆分問題,可以將虛點建成一個如下圖所示的類似線段樹的結構,使得區間的拆分可以放到線段樹上進行。
//知識點:線段樹優化建圖
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 4e5 + 10;
const LL kInf = 0x3f3f3f3f3f3f3f3f;
//=============================================================
int n, q, start, node_num;
int e_num, head[kN], v[kN << 4], w[kN << 4], ne[kN << 4];
bool vis[kN];
LL dis[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Add(int u_, int v_, int w_) {
v[++ e_num] = v_, w[e_num] = w_;
ne[e_num] = head[u_], head[u_] = e_num;
}
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
struct SegmentTree {
int root, lson[kN], rson[kN];
void Build(int &now_ ,int L_, int R_, bool type_) {
if (L_ == R_) {
now_ = L_;
return ;
}
now_ = ++ node_num;
Build(ls, L_, mid, type_);
Build(rs, mid + 1, R_, type_);
if (!type_) Add(now_, ls, 0), Add(now_, rs, 0);
if (type_) Add(ls, now_, 0), Add(rs, now_, 0);
}
void Modify(int now_, int L_, int R_, int l_, int r_, int u_, int w_,
bool type_) {
if (l_ <= L_ && R_ <= r_) {
if (!type_) Add(u_, now_, w_);
if (type_) Add(now_, u_, w_);
return ;
}
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, u_, w_, type_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, u_, w_, type_);
}
} Seg[2];
#undef ls
#undef rs
#undef mid
void Init() {
node_num = n;
Seg[0].Build(Seg[0].root, 1, n, 0);
Seg[1].Build(Seg[1].root, 1, n, 1);
}
void Spfa(int s_) { //他死了
std::queue <int> q;
memset(dis, 0x3f, sizeof(dis));
dis[s_] = 0;
q.push(s_);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u] + w_ < dis[v_]) {
dis[v_] = dis[u] + w_;
q.push(v_);
}
}
}
}
#define pr std::pair
#define mp std::make_pair
void Dijkstra(int s_) {
std::priority_queue <pr <LL, int> > q;
memset(dis, 63, sizeof (dis));
dis[s_] = 0;
q.push(mp(0, s_));
while (! q.empty()) {
int u_ = q.top().second; q.pop();
if (vis[u_]) continue;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
q.push(mp(-dis[v_], v_));
}
}
}
}
#undef pr
#undef mp
//=============================================================
int main() {
n = read(), q = read(), start = read();
Init();
while (q --) {
int opt = read();
if (opt == 1) {
int u_ = read(), v_ = read(), w_ = read();
Add(u_, v_, w_);
} else {
int u_ = read(), l_ = read(), r_ = read(), w_ = read();
int type = (opt == 3);
Seg[type].Modify(Seg[type].root, 1, n, l_, r_, u_, w_, type);
}
}
Dijkstra(start);
for (int i = 1; i <= n; ++ i) {
printf("%lld ", dis[i] < kInf ? dis[i] : -1);
}
return 0;
}
字符串
Trie
字典樹。
namespace Trie {
const int kMaxNode = kN << 5;
int node_num, tr[kMaxNode][2];
LL val[kMaxNode];
void Insert(LL val_) {
int now_ = 0;
for (LL i = 33; i >= 0; -- i) {
int ch = val_ >> i & 1ll;
if (!tr[now_][ch]) tr[now_][ch] = ++ node_num;
now_ = tr[now_][ch];
}
val[now_] = val_;
}
}
可持久化 Trie
Q:什么時候需要可持久化?
A:想建一車 Trie 但是建不下的時候。
其實我很疑惑該把這個加到數據結構還是字符串里的= =
可持久化 Trie 可以將全局的二進制運算問題轉化到區間上進行。
//知識點:
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 6e6 + 10;
//=============================================================
int n, m, a[kN], sum[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace Trie {
#define ls (tr[now_][0])
#define rs (tr[now_][1])
const int kMaxRoot = kN;
const int kMaxNode = kN << 2;
int node_num, root[kMaxRoot], tr[kMaxNode][2], last[kMaxNode];
void Insert(int &now_, int pre_, int val_, int lth_, int id_) {
now_ = ++ node_num;
ls = tr[pre_][0], rs = tr[pre_][1], last[now_] = id_;
if (lth_ < 0) return ;
int ch = val_ >> lth_ & 1;
Insert(tr[now_][ch], tr[pre_][ch], val_, lth_ - 1, id_);
}
int Query(int now_, int val_, int lth_, int l_) {
if (lth_ < 0) return sum[last[now_]];
int ch = val_ >> lth_ & 1;
if (last[tr[now_][ch ^ 1]] >= l_) {
return Query(tr[now_][ch ^ 1], val_, lth_ - 1, l_);
}
return Query(tr[now_][ch], val_, lth_ - 1, l_);
}
}
void Init() {
n = read(), m = read();
Trie::last[0] = -1;
Trie::Insert(Trie::root[0], 0, 0, 25, 0);
for (int i = 1; i <= n; ++ i) {
sum[i] = sum[i - 1] ^ read();
Trie::Insert(Trie::root[i], Trie::root[i - 1], sum[i], 25, i);
}
}
//=============================================================
int main() {
Init();
while (m --) {
char opt[5]; scanf("%s", opt + 1);
if (opt[1] == 'A') {
++ n;
sum[n] = sum[n - 1] ^ read();
Trie::Insert(Trie::root[n], Trie::root[n - 1], sum[n], 25, n);
} else {
int l = read(), r = read(), val = sum[n] ^ read();
printf("%d\n", Trie::Query(Trie::root[r - 1], val, 25, l - 1) ^ val);
}
}
return 0;
}
KMP
子串匹配問題。在朴素算法中,如果在某一位上失配,則會拋棄當前已匹配的部分,跳到下一個位置再從開頭進行匹配。
而 KMP 利用了當前已匹配的部分,使得在下一個位置時不必從開頭進行匹配,從而對朴素算法進行了加速。
//知識點:KMP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
char s1[kN], s2[kN];
int n1, n2;
int fail[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
//=============================================================
int main() {
scanf("%s", s1 + 1);
scanf("%s", s2 + 1);
n1 = strlen(s1 + 1), n2 = strlen(s2 + 1);
fail[1] = 0;
for (int i = 2, j = 0; i <= n2; ++ i) {
while (j > 0 && s2[i] != s2[j + 1]) j = fail[j];
if (s2[i] == s2[j + 1]) ++ j;
fail[i] = j;
}
for (int i = 1, j = 0; i <= n1; ++ i) {
while (j > 0 && (j == n2 || s1[i] != s2[j + 1])) j = fail[j];
if (s1[i] == s2[j + 1]) ++ j;
if (j == n2) printf("%d\n", i - n2 + 1);
}
for (int i = 1; i <= n2; ++ i) printf("%d ", fail[i]);
return 0;
}
AC 自動機
可以認為是 KMP 算法在 Trie 樹上的應用,與 KMP 算法在失配時應用已匹配部分的 border 進行跳轉類似,AC 自動機在失配時會根據失配指針跳轉到 Trie 樹上代表已匹配部分的 border 的節點,從而加速匹配。
//知識點:ACAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define LL long long
const int kT = 2e6 + 10;
const int kN = 2e5 + 10;
//=============================================================
int n;
char s[kT];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
namespace ACAM {
std::vector <int> id[kN];
int node_num, tr[kN][26], sum[kN], fail[kN];
int e_num, size[kN], head[kN], v[kN], ne[kN];
void Add(int u_, int v_) {
v[++ e_num] = v_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
void Dfs(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
Dfs(v_);
size[u_] += size[v_];
}
for (int i = 0, lim = id[u_].size(); i < lim; ++ i) {
sum[id[u_][i]] = size[u_];
}
}
void Insert(int id_, char *s_) {
int now = 0, lth = strlen(s_ + 1);
for (int i = 1; i <= lth; ++ i) {
if (! tr[now][s_[i] - 'a']) tr[now][s_[i] - 'a'] = ++ node_num;
now = tr[now][s_[i] - 'a'];
}
id[now].push_back(id_);
}
void Build() {
std::queue <int> q;
for (int i = 0; i < 26; ++ i) {
if (tr[0][i]) q.push(tr[0][i]);
}
while (! q.empty()) {
int u_ = q.front(); q.pop();
for (int i = 0; i < 26; ++ i) {
if (tr[u_][i]) {
fail[tr[u_][i]] = tr[fail[u_]][i];
q.push(tr[u_][i]);
} else {
tr[u_][i] = tr[fail[u_]][i];
}
}
}
}
void Query(char *t_) {
int now = 0, lth = strlen(t_ + 1);
for (int i = 1; i <= lth; ++ i) {
now = tr[now][t_[i] - 'a'];
++ size[now];
}
for (int i = 1; i <= node_num; ++ i) Add(fail[i], i);
Dfs(0);
for (int i = 1; i <= n; ++ i) printf("%d\n", sum[i]);
}
}
//=============================================================
int main() {
n = read();
for (int i = 1; i <= n; ++ i) {
scanf("%s", s + 1);
ACAM::Insert(i, s);
}
ACAM::Build();
scanf("%s", s + 1);
ACAM::Query(s);
return 0;
}
最小表示法
當字符串 \(S\) 中可以選定一個位置 \(i\) 滿足:
則稱 \(S\) 與 \(T\) 循環同構。
字符串 \(S\) 的 最小表示 為與 \(S\) 循環同構的所有字符串中字典序最小的字符串。
最小表示法思想與 KMP 類似,都是重復利用之前比較過的部分,減少時間復雜度。
學習筆記:「最小表示法」P1368 工藝 /【模板】最小表示法
//知識點:最小表示法
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 3e5 + 10;
//=============================================================
int n, a[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int MinRep() {
int i = 0, j = 1, k = 0;
while (i < n && j < n && k < n) {
int delta = a[(i + k) % n] - a[(j + k) % n];
if (delta == 0) {
++ k;
continue ;
}
if (delta > 0) i += k + 1;
else j += k + 1;
if (i == j) j ++;
k = 0;
}
return std :: min(i, j);
}
//=============================================================
int main() {
n = read();
for (int i = 0; i < n; ++ i) a[i] = read();
for (int i = MinRep(), j = 0; j < n; ++ j) {
printf("%d ", a[(i + j) % n]);
}
return 0;
}
后綴數組
字符串 \(S\) 的后綴數組定義為兩個數組 \(sa,rk\)。
\(sa\) 儲存 \(S\) 的所有后綴 按字典序排序后的起始下標,滿足 \(S[sa_{i-1}:n]<S[sa_i:n]\)。
\(rk\) 儲存 \(S\) 的所有后綴的排名。
結合后綴的最長公共前綴,后綴數組可以用來處理許多復雜度字符串問題。
//知識點:SA
/*
By:Luckyblock
I love Marisa;
But Marisa has died;
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
//=============================================================
char S[kMaxn];
//rk[i]: 倍增過程中子串[i:i+2^k-1]的排名,
//sa[i] 排名為i的子串 [i:i+2^k-1],
//它們互為反函數。
//存在越界風險,如果不寫特判,rk 和 oldrk 要開 2 倍空間。
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1];
int id[kMaxn], cnt[kMaxn]; //用於計數排序的兩個 temp 數組
int height[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) {
k = 0;
} else {
if (k) -- k;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
//=============================================================
int main() {
scanf("%s", S + 1);
n = strlen(S + 1);
m = std :: max(n, 300); //值域大小
//初始化 rk 和 sa
for (int i = 1; i <= n; ++ i) ++ cnt[rk[i] = S[i]];
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
//倍增過程。w 是已經推出的子串長度,數值上等於上述分析中的 2^{k-1}。
//注意此處的 sa 數組存的並不是后綴的排名,存的是倍增過程中指定長度子串的排名。
for (int w = 1; w < n; w <<= 1) {
//按照后半截 rk[i+w] 作為第二關鍵字排序。
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) id[i] = i;
for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i] + w]]; //越界風險,2倍空間
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i] + w]] --] = id[i];
//按照前半截 rk[i] 作為第一關鍵字排序。
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) id[i] = sa[i];
for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i]]];
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i]]] --] = id[i];
//更新 rk 數組,可以滾動數組一下,但是可讀性會比較差(
for (int i = 1; i <= n; ++ i) oldrk[i] = rk[i];
for (int p = 0, i = 1; i <= n; ++ i) {
if (oldrk[sa[i]] == oldrk[sa[i - 1]] && //判斷兩個子串是否相等。
oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) { //越界風險,2倍空間
rk[sa[i]] = p;
} else {
rk[sa[i]] = ++ p;
}
}
}
for (int i = 1; i <= n; ++ i) printf("%d ", sa[i]);
printf("\n");
GetHeight();
for (int i = 2; i <= n; ++ i) printf("%d ", height[i]);
return 0;
}
后綴自動機
字符串 \(S\) 的后綴自動機 (suffix automaton, SAM) 是一個可以且盡可以接受 \(S\) 所有后綴的 最小的 DFA。
更形式化的定義:
- 字符串 \(S\) 的 SAM 是一張 DAWG(有向單詞無環圖)。節點被稱作 狀態,邊被稱作狀態間的 轉移。
- 存在一個起始節點,稱作 起始狀態。其它節點均可從起始節點出發到達。
- 每個 轉移 都標有一些字母。從一個節點出發的所有轉移均 不同。
- 存在數個 終止狀態。若從 \(t_0\) 出發,最終轉移到一個終止狀態,路徑上所有轉移連接起來一定是 \(S\) 的一個后綴。\(S\) 的每個后綴均可用一條 從起始節點到某個終止狀態 的路徑構成。
- 在所有滿足上述條件的 DFA 中,SAM 的節點數是最少的。
SAM 並不是一個典型的 DFA,在 DAWG 基礎上,除 \(t_0\) 外的每個狀態都被添加了一條 后綴鏈接。所有后綴鏈接組成了樹狀結構,這棵樹被稱為 parent 樹。
字符串 \(S\) 的 SAM 能包含 \(S\) 所有子串的信息。
SAM 將這些信息以高度壓縮的形式儲存,對於一個長度為 \(n\) 的字符串,它的 SAM 空間復雜度僅為 \(O(n)\),構建 SAM 的時間復雜度也僅為 \(O(n)\)。
//知識點:SAM
/*
By:Luckyblock
試了試變量寫法,挺清爽的。
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, last = 1, node_num = 1; //注意 1 為起始狀態,其他狀態的標號從 1 開始。
int ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1]; //SAM
int edge_num, v[kMaxn << 1], ne[kMaxn << 1], head[kMaxn << 1]; //Graph
ll ans, size[kMaxn << 1];
char S[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(ll &fir, ll sec) {
if (sec > fir) fir = sec;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Insert(int c_) { //原串 S -> 新串 S + c
int p = last, now = last = ++ node_num;
size[now] = 1, len[now] = len[p] + 1; //僅為終止狀態 size 賦值。
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
//case 1:
if (! p) {link[now] = 1; return ;}
//case 2:
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return ;}
//case 3:
int newq = ++ node_num;
len[newq] = len[p] + 1;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q];
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
//parent 樹上 DP,用孩子更新父親。
//孩子的長度比父親長,則孩子出現的地方,父親一定出現,size 累加。
//父親的 size 也可能不為 0(為終止狀態)。
void Dfs(int u) {
for (int i = head[u]; i; i = ne[i]) {
Dfs(v[i]);
size[u] += size[v[i]];
}
if (size[u] > 1) GetMax(ans, 1ll * size[u] * len[u]);
}
//=============================================================
int main() {
scanf("%s", S + 1);
int n = strlen(S + 1);
for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs(1);
printf("%lld\n", ans);
return 0;
}
廣義后綴自動機
廣義 SAM 是一種用於維護 Trie 的子串信息的 SAM 的簡單變體。
將多個模式串插入到 Trie 后,即可使用廣義 SAM 維護多模式串的信息。這是廣義 SAM 最廣泛的應用之一,本文的重點也在於此。其基本思想是將多串的信息進行壓縮,使得 SAM 在仍滿足節點數最少的同時 包含所有子串的信息。此時 SAM 中的一個狀態可能同時代表多個串中相應的子串。
//知識點:SAM
/*
By:Luckyblock
試了試變量寫法,挺清爽的。
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
ll ans;
char S[kMaxn];
int node_num = 1, ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
int Insert(int c_, int last_) {
if (ch[last_][c_]) {
int p = last_, q = ch[p][c_];
if (len[p] + 1 == len[q]) return q;
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
len[newq] = len[p] + 1;
link[newq] = link[q];
link[q] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return newq;
}
int p = last_, now = ++ node_num;
len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return now;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return now;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return now;
}
//=============================================================
int main() {
int T = read();
while (T --) {
scanf("%s", S + 1);
int n = strlen(S + 1), last = 1;
for (int i = 1; i <= n; ++ i) {
last = Insert(S[i] - 'a', last);
}
}
for (int i = 2; i <= node_num; ++ i) {
ans += len[i] - len[link[i]];
}
printf("%lld\n", ans);
return 0;
}
數學
快速冪
轉化為 \(\prod a^{2^?}\)。
//知識點:快速冪
/*
By:Luckyblock
注意 p = 0
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
LL a, b, p;
//=============================================================
inline LL read() {
LL f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3ll) + (w << 1ll) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_, LL mod_) {
LL ret = 1;
x_ %= mod_;
for (; y_; y_ >>= 1ll) {
if (y_ & 1) ret = ret * x_ % mod_;
x_ = x_ * x_ % mod_;
}
return ret % mod_;
}
//=============================================================
int main() {
LL a = read(), b = read(), p = read();
printf("%lld^%lld mod %lld=%lld", a, b, p, QPow(a, b, p));
return 0;
}
線性篩
每個數只被它的最小質因子篩掉。
//知識點:先行篩
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e8 + 10;
//=============================================================
int n, q;
int p_num, p[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void GetPrime(int n_) {
for (int i = 2; i <= n_; ++ i) {
if (! vis[i]) {
p[++ p_num] = i;
}
for (int j = 1; j <= p_num; ++ j) {
if (i * p[j] > n_) break;
vis[i * p[j]] = true;
if (i % p[j] == 0) break;
}
}
}
//=============================================================
int main() {
n = read(), q = read();
GetPrime(n);
while (q --) {
int k = read();
printf("%d\n", p[k]);
}
return 0;
}
線性求逆元
若 \(p\in \mathbb{P}\),則有:
//知識點:線性求逆元
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 3e6 + 10;
//=============================================================
int n, p, inv[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
n = read(), p = read();
inv[1] = 1;
for (int i = 2; i <= n; ++ i) {
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
}
for (int i = 1; i <= n; ++ i) {
printf("%d\n", inv[i]);
}
return 0;
}
矩陣快速冪
矩陣乘法:
矩陣滿足結合律,故可拆分為 \(\prod A^{2^?}\),使用快速冪求解。
//知識點:矩陣乘法,矩陣快速冪
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxm = 100 + 1;
const LL kMod = 1e9 + 7;
//=============================================================
int n;
LL b;
struct Matrix {
LL a[kMaxm][kMaxm];
void build() {
for (int i = 1; i <= n; ++ i) {
a[i][i] = 1;
}
}
Matrix operator * (const Matrix &b_) const {
Matrix ret;
memset(ret.a, 0, sizeof (ret.a));
for (int k = 1; k <= n; ++ k) {
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
ret.a[i][j] += a[i][k] * b_.a[k][j] % kMod;
ret.a[i][j] %= kMod;
}
}
}
return ret;
}
} a;
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
Matrix QMatrixPow(Matrix a_, LL b_) {
Matrix ret;
ret.build();
for (; b_; b_ >>= 1) {
if (b_ & 1) ret = ret * a_;
a_ = a_ * a_;
}
return ret;
}
//=============================================================
int main() {
n = read(); scanf("%lld", &b);
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
a.a[i][j] = read();
}
}
Matrix ans = QMatrixPow(a, b);
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
printf("%d ", ans.a[i][j]);
}
printf("\n");
}
return 0;
}
三分法
之前講課的課件:「二分與三分」課件配套資料。
//知識點:三分法求函數極值
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 13 + 5;
const double eps = 1e-7;
//=============================================================
int n;
double l, r, a[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
double f(double x_) {
double ret = 0;
for (int i = 1; i <= n + 1; ++ i) {
ret = ret * x_ + a[i];
}
return ret;
}
//=============================================================
int main() {
n = read();
scanf("%lf %lf", &l, &r);
for (int i = 1; i <= n + 1; ++ i) scanf("%lf", &a[i]);
while (r - l > eps) {
double lx = (2.0 * l + r) / 3, rx = (l + 2.0 * r) / 3;
if (f(lx) < f(rx)) {
l = lx;
} else {
r = rx;
}
}
printf("%.5lf", l);
return 0;
}
高斯消元
小學解方程。
//知識點:高斯消元
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
const double eps = 1e-7;
const int kMaxn = 100 + 10;
//=============================================================
int n;
double a[kMaxn][kMaxn], ans[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
n = read();
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n + 1; ++ j) {
scanf("%lf", &a[i][j]);
}
}
for (int i = 1; i <= n; ++ i) { //每次將第 i 行第 i 個元素消為 1。
int pos = i; //每次找到最大系數消除,減小誤差
for (int j = i + 1; j <= n; ++ j) {
if (fabs(a[j][i]) > fabs(a[pos][i])) {
pos = j;
}
}
if (fabs(a[pos][i]) < eps) { //系數為 0,無解
printf("No Solution");
return 0;
}
for (int j = 1; j <= n + 1; ++ j) { //交換到第 i 行
std::swap(a[pos][j], a[i][j]);
}
double tmp = a[i][i]; //將第 i 行第 i 個消成 1,同時處理該行其他系數
for (int j = i; j <= n + 1; ++ j) {
a[i][j] /= tmp;
}
for (int j = i + 1; j <= n; ++ j) { //枚舉其他行
tmp = a[j][i]; //該行對應系數
for (int k = i; k <= n + 1; ++ k) {
a[j][k] -= a[i][k] * tmp; //注意第 i 行的系數均已被處理過
}
}
}
ans[n] = a[n][n + 1];
for (int i = n - 1; i >= 1; -- i) {
ans[i] = a[i][n + 1];
for (int j = i + 1; j <= n; ++ j) { //回帶
ans[i] -= a[i][j] * ans[j];
}
}
for (int i = 1; i <= n; ++ i) {
printf("%.2lf\n", ans[i]);
}
return 0;
}
盧卡斯定理
//知識點:Lucas
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxp = 1e5 + 10;
//=============================================================
LL p, fac[kMaxp];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
x_ %= p;
LL ret = 1ll;
for (; y_; y_ >>= 1ll) {
if (y_ & 1) ret = ret * x_ % p;
x_ = x_ * x_ % p;
}
return ret;
}
LL C(LL n_, LL m_) {
if (m_ > n_) return 0ll;
return fac[n_] * QPow(fac[m_], p - 2ll) * QPow(fac[n_ - m_], p - 2ll);
}
LL Lucas(LL n_, LL m_) {
if (! m_) return 1;
return Lucas(n_ / p, m_ / p) * C(n_ % p, m_ % p) % p;
}
//=============================================================
int main() {
int T = read();
while (T --) {
int n = read(), m = read();
p = read();
fac[0] = 1;
for (int i = 1; i <= p; ++ i) {
fac[i] = 1ll * fac[i - 1] * i % p;
}
printf("%lld\n", Lucas(n + m, m));
}
return 0;
}
數學亂記
懶得往這搬了= =
雜項
CDQ 分治
Cdq 分治是一種牛逼思想,一般用於解決區間點對問題。設當前處理的區間為 \([l, r]\),Cdq 分治的一般過程:
- 若 \(l = r\),返回。
- 設區間中點為 \(mid\),遞歸處理 \([l,mid]\) 和 \([mid + 1, r]\)。
- 不橫跨 \(mid\) 的點對都會在遞歸中被解決,僅考慮橫跨 \(mid\) 的點對的貢獻。
個人理解:
將 \(O(n^2)\) 級別的點對統計,利用單調性確定合法性,變為 \(O(n\log^k n)\) 級別。
太神了!
//知識點:CDQ分治
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
#define lowbit(x) (x&-x)
#define mid ((l_+r_)>>1)
const int kMaxn = 1e5 + 10;
//=============================================================
struct Data {
int x, y, z, cnt, ans;
} a[kMaxn];
int n, k, ans[kMaxn], t[kMaxn << 1];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
bool Compare1(Data fir_, Data sec_) {
if (fir_.x != sec_.x) return fir_.x < sec_.x;
if (fir_.y != sec_.y) return fir_.y < sec_.y;
return fir_.z < sec_.z;
}
bool Compare2(Data fir_, Data sec_) {
if (fir_.y != sec_.y) return fir_.y < sec_.y;
return fir_.z < sec_.z;
}
void Add(int pos_, int val_) {
for (; pos_ <= k; pos_ += lowbit(pos_)) {
t[pos_] += val_;
}
}
int Sum(int pos_) {
int ret = 0;
for (; pos_; pos_ -= lowbit(pos_)) ret += t[pos_];
return ret;
}
void Cdq(int l_, int r_) {
if (l_ == r_) return ;
Cdq(l_, mid), Cdq(mid + 1, r_);
std :: sort(a + l_, a + mid + 1, Compare2);
std :: sort(a + mid + 1, a + r_ + 1, Compare2);
int i = l_, j = mid + 1;
for (; j <= r_; ++ j) {
for (; a[i].y <= a[j].y && i <= mid; ++ i) {
Add(a[i].z, a[i].cnt);
}
a[j].ans += Sum(a[j].z);
}
for (j = l_; j < i; ++ j) Add(a[j].z, - a[j].cnt);
}
bool JudgeEqual(Data fir_, Data sec_) {
if (fir_.x != sec_.x) return false;
if (fir_.y != sec_.y) return false;
return fir_.z == sec_.z;
}
//=============================================================
int main() {
int tmpn = n = read();
k = read();
for (int i = 1; i <= n; ++ i) {
a[i] = (Data) {read(), read(), read()};
}
std :: sort(a + 1, a + n + 1, Compare1);
n = 0;
for (int i = 1, cnt = 0; i <= tmpn; ++ i) {
cnt ++;
if (! JudgeEqual(a[i], a[i + 1])) {
a[++ n] = a[i],
a[n].cnt = cnt,
cnt = 0;
}
}
Cdq(1, n);
for (int i = 1; i <= n; ++ i) {
ans[a[i].ans + a[i].cnt - 1] += a[i].cnt;
}
for (int i = 0; i < tmpn; ++ i) {
printf("%d\n", ans[i]);
}
return 0;
}