正式退役了,谨以此文记录三年的竞赛生涯。

字符串

HASH

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

ull base = 131;
ull a[10010];
ll prime = 233317; 
ull mod = 212370440130137957ll;

ull hashe(string ss) {
    ll len = ss.length();
    ull anss = 0;
    for (ll i = 0; i < len; i ++ )
        anss = (anss * base + (ull)ss[i]) % mod + prime;
    return anss;
}

int main() {
    ll n,ans = 0;
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n;
    for(ll i = 1; i <= n; i ++ ) {
        string str; cin >> str;
        a[i] = hashe(str);
    }
    sort(a + 1, a + n + 1);
    for(ll i = 1; i <= n; i ++ ) {
        if(a[i] != a[i - 1]) ans ++ ;
    }
    cout << ans << "\n";
    return 0;
}

KMP

ll nxt[MAX], nxt1[MAX];
void Solve() {
    string pat, txt;
    cin >> txt >> pat;
    nxt[0] = -1; 
    for(ll i = 1, j = -1; i < pat.size(); i ++ ) {
        while(j != -1 && pat[i] != pat[j + 1]) j = nxt[j];
        if(pat[i] == pat[j + 1]) nxt[i] = nxt[ ++ j] ; 
        if(pat[i + 1] != pat[j + 1] || pat[i] != pat[j]) nxt[i] = j;
    }
    nxt1[0] = -1; 
        for(ll i = 1, j = -1; i < pat.size(); i ++ ) {
        while(j != -1 && pat[i] != pat[j + 1]) j = nxt1[j];
        if(pat[i] == pat[j + 1]) j ++ ;
        nxt1[i] = j;
    }
    for(ll i = 0, j = -1; i < txt.size(); i ++ ) {
        while(j != -1 && txt[i] != pat[j + 1]) j = nxt[j];
        if(txt[i] == pat[j + 1]) j ++ ;
        if(j == pat.size() - 1) cout << i - j + 1 << "\n";
    }
    for(ll i = 0; i < pat.size(); i ++ ) cout << nxt1[i] + 1 << " ";
}

Z函数

ll si = str.size();    
vector<ll> z(si + 1);
ll l = 0, r = 0;
for(ll i = 1; i < si; i ++ ) { // 右端点最靠右的匹配段,l记录起点,r记录终点
    if(i <= r && z[i - l] < r - i + 1) {
        z[i] = z[i - l];
    } else {
        z[i] = max((ll)0, r - i + 1);
        while(i + z[i] < si && str[z[i]] == str[i + z[i]]) ++ z[i];
    }
    if(i + z[i] - 1 > r) {
        l = i; r = i + z[i] - 1;
    } 
}

字典树

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAX = 3e6 + 10;        // 定义最大节点数
const int CHARSET_SIZE = 62;     // 字符集大小(大写字母26个,小写字母26个,数字10个)

struct Trie {
    int nex[MAX][CHARSET_SIZE];  // 存储子节点索引
    bool exis[MAX];              // 标记是否有单词在此结束
    int cnt;                     // 当前使用的节点数
    int sum[MAX];   

    void init() {
        cnt = 0;
        memset(nex, 0, sizeof(nex));
        memset(exis, false, sizeof(exis));
        memset(sum, 0, sizeof(sum));
    }
    inline int getnum(char x) {  // 映射字符到索引
        if (x >= 'A' && x <= 'Z')
            return x - 'A';
        else if (x >= 'a' && x <= 'z')
            return x - 'a' + 26;
        else
            return x - '0' + 52;
    }
    inline void insert(const string &s) {
        int pos = 0;
        for (char c : s) {  
            int index = getnum(c);
            if (!nex[pos][index]) 
                nex[pos][index] = ++ cnt;
            pos = nex[pos][index];
            sum[pos] ++ ;
        }   
        exis[pos] = true;
    }
    inline int find(const string &s) {
        int pos = 0;
        for (char c : s) {
            int index = getnum(c);
            if (!nex[pos][index])
                return false;
            pos = nex[pos][index];
        }
        return sum[pos];
    }
}t;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    ll T; cin >> T;
    while (T--) {
        t.init();
        ll n, q;
        cin >> n >> q;
        string s;
        for (ll i = 0; i < n; i++) {
            cin >> s;
            // trie.insert(s);
            t.insert(s);
        }
        for (ll i = 0; i < q; i++) {
            cin >> s;
            // if (trie.find(s)) ans++;
            cout << t.find(s) << "\n";
        }
    }
    return 0;
}

AC自动机

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 1e6 + 10;

namespace AC {
    int trie[maxn][26], cnt;     // 字典树
    int sum[maxn], fail[maxn];   // 节点字符串的个数,fail指针
    int in[maxn], vis[maxn];     // 拓扑排序优化
    int num[maxn], ans[maxn];    // 节点指向的字符串序号 出现次数

    void init() {
        cnt = 0;
        for(ll i = 0; i < maxn; i ++ ) {
            sum[i] = 0; fail[i] = 0; in[i] = 0; vis[i] = 0; num[i] = 0; ans[i] = 0;
            for(ll j = 0; j < 26; j ++ ) trie[i][j] = 0;
        }
    }

    void insert(string pat, ll i) { // 插入
        int pos = 0;
        for(auto c : pat) {
            if(!trie[pos][c - 'a']) trie[pos][c - 'a'] = ++ cnt;
            pos = trie[pos][c - 'a'];    
        }
        num[pos] = i;
    } 

    void getfail() {            // bfs求失配指针
        queue<int> q;
        for(ll v = 0; v < 26; v ++ ) { // 预处理第二层,此时所有子节点的fail指向root
            int c = trie[0][v];
            if(c) {
                fail[c] = 0;
                q.push(c);
            }
        }
        while(!q.empty()) {
            int pos = q.front(); q.pop();
            int f = fail[pos]; // 父节点的fail指针
            for(ll v = 0; v < 26; v ++ ) {
                int c = trie[pos][v];
                if(c) {
                    fail[c] = trie[f][v]; // 如果父节点的fail指针下有当前字符,则指向它,否则指向root,而trie[f][v]默认值为0
                    in[fail[c]] ++ ;      // 拓扑序优化
                    q.push(c);
                } else trie[pos][v] = trie[f][v]; // “造”一个孩子,跳过去匹配
            }
        }   
    }
    
    void query(string &txt) {
        int pos = 0;
        for(auto c : txt) {
            pos = trie[pos][c - 'a']; vis[pos] ++ ; ans[num[pos]] = vis[pos];
        }
    }

    void topu() {
        queue<int> q;
        for(int i = 1; i <= cnt; i ++ ) {
            if(in[i] == 0) q.push(i);
        }
        while(!q.empty()) {
            int u = q.front(); q.pop();
            ans[num[u]] = vis[u];
            int v = fail[u]; in[v] -- ;
            vis[v] += vis[u];
            if(in[v] == 0) q.push(v);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    AC::init();
    ll n; cin >> n;
    vector<string> pat(n + 1);
    map<string, int> mp;
    for(ll i = 1; i <= n; i ++ ) {
        cin >> pat[i];
        if(mp[pat[i]] == 0) {
            AC::insert(pat[i], i);
            mp[pat[i]] = i;
        }
    }
    AC::getfail();
    string txt; cin >> txt; 
    AC::query(txt);
    AC::topu();
    for(ll i = 1; i <= n; i ++ ) cout << AC::ans[mp[pat[i]]] << "\n";
    return 0;
}

Manacher

vector<int> d1(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
  int k = (i > r) ? 1 : min(d1[l + r - i], r - i + 1);
  while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
    k++;
  }
  d1[i] = k--;
  if (i + k > r) {
    l = i - k;
    r = i + k;
  }
}

vector<int> d2(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
  int k = (i > r) ? 0 : min(d2[l + r - i + 1], r - i + 1);
  while (0 <= i - k - 1 && i + k < n && s[i - k - 1] == s[i + k]) {
    k++;
  }
  d2[i] = k--;
  if (i + k > r) {
    l = i - k - 1;
    r = i + k;
  }
}

数据结构

树状数组

ll Lowbit(ll x) { // lowbit是求得区间范围 
    return x & -x;
}

// 在x位置add上val,从赋值处往上修改 u+lowbit(u)  
void Update(ll x,ll val,vector<ll> &c,ll n) {
    for(; x <= n; x += Lowbit(x)) c[x] += val;
}


ll Sum(ll x,vector<ll> &c,ll n) { 
    ll sum = 0;
    for(; x > 0; x -= Lowbit(x)) sum += c[x];
    return sum;
}

void Solve() {
    cin >> n >> m;
    vector<ll> c(n + 1,0);
    for(ll i = 1; i <= n; i++) {
        cin >> arr;
        Update(i, arr, c, n);
    }
    for(ll i = 1;i <= m;i++) {
        cin >> f >> x >> y;
        if(f == 2) {
            cout << Sum(y, c, n) - Sum(x - 1, c, n) << "\n";
        }
        else Update(x, y, c, n);
    }
}

单调栈

stack<ll> s;
int main() {
    cin >> n;
    for(ll i = 1; i <= n; i ++ ) cin >> a[i];
    for(ll i = n; i >= 1; i -- ) {
        while(!s.empty() && a[s.top()] <= a[i]) s.pop(); 
        f[i] = (s.empty() ? 0 : s.top());
        s.push(i);
    }
    for(ll i = 1; i <= n; i ++ ) cout << f[i] << " ";
    return 0;
}

最小生成树

// Prim
#include<bits/stdc++.h>
using namespace std;
#define re register
#define il inline
il int read()
{
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}//快读,不理解的同学用cin代替即可
#define inf 123456789
#define maxn 5005
#define maxm 200005
struct edge
{
    int v,w,next;
}e[maxm<<1];
//注意是无向图,开两倍数组
int head[maxn],dis[maxn],cnt,n,m,tot,now=1,ans;
//已经加入最小生成树的的点到没有加入的点的最短距离,比如说1和2号节点已经加入了最小生成树,那么dis[3]就等于min(1->3,2->3)
bool vis[maxn];
//链式前向星加边
il void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
}
//读入数据
il void init()
{
    n=read(),m=read();
    for(re int i=1,u,v,w;i<=m;++i)
    {
        u=read(),v=read(),w=read();
        add(u,v,w),add(v,u,w);
    }
}
il int prim()
{
    //先把dis数组附为极大值
    for(re int i=2;i<=n;++i)
    {
        dis[i]=inf;
    }
    //这里要注意重边,所以要用到min
    for(re int i=head[1];i;i=e[i].next)
    {
        dis[e[i].v]=min(dis[e[i].v],e[i].w);
    }
    while(++tot<n)//最小生成树边数等于点数-1
    {
        re int minn=inf;//把minn置为极大值
        vis[now]=1;//标记点已经走过
        //枚举每一个没有使用的点
        //找出最小值作为新边
        //注意这里不是枚举now点的所有连边,而是1~n
        for(re int i=1;i<=n;++i)
        {
            if(!vis[i]&&minn>dis[i])
            {
                minn=dis[i];
                now=i;
            }
        }
        ans+=minn;
        //枚举now的所有连边,更新dis数组
        for(re int i=head[now];i;i=e[i].next)
        {
            re int v=e[i].v;
            if(dis[v]>e[i].w&&!vis[v])
            {
                dis[v]=e[i].w;
            }
        }
    }
    return ans;
}
int main()
{
    init();
    printf("%d",prim());
    return 0;
}
// Kruskal
ll n, m;
struct edge {
    ll x, y, z;
}e[M];
bool cmp(edge u, edge v) {
    return u.z < v.z;
}
ll fa[N];
void init() {
    for(ll i = 1; i <= n; i ++ ) fa[i] = i;
}
ll find(ll x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void Kruskal() {
    ll ans = 0, cnt = 0;
    sort(e + 1, e + m + 1, cmp); init();
    for(ll i = 1; i <= m; i ++ ) {
        ll x = e[i].x, y = e[i].y, z = e[i].z;
        ll fx = find(x), fy = find(y);
        if(fx == fy) continue;
        fa[fx] = fy;
        ans += z; cnt ++ ;
    }
    if(cnt < n - 1) cout << "orz" << "\n";
    else cout << ans << "\n";
}
void Solve() {
    cin >> n >> m;
    for(ll i = 1; i <= m; i ++ ) {
        ll x, y, z;
        cin >> x >> y >> z;
        e[i].x = x; e[i].y = y; e[i].z = z;
    }
    Kruskal();
}

线段树

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAX = 5e5 + 10;
ll n, m, l, x, y, k;
ll a[MAX];
ll ans;

struct segment_tree {
    struct Tree {
        ll l, r;
        ll sum;
        ll lazy;
    } tr[MAX * 4];
    void build(ll i, ll l, ll r) {
        tr[i] = {l, r, 0, 0};
        if (l == r) {
            tr[i].sum = a[l];
            return;
        }
        ll mid = (l + r) >> 1;
        build(i << 1, l, mid);
        build(i << 1 | 1, mid + 1, r);
        tr[i].sum = tr[i << 1].sum + tr[i << 1 | 1].sum;
    }
    void pushdown(ll i) {
        if (tr[i].lazy) {
            tr[i << 1].sum += tr[i].lazy * (tr[i << 1].r - tr[i << 1].l + 1);
            tr[i << 1 | 1].sum += tr[i].lazy * (tr[i << 1 | 1].r - tr[i << 1 | 1].l + 1);
            tr[i << 1].lazy += tr[i].lazy;
            tr[i << 1 | 1].lazy += tr[i].lazy;
            tr[i].lazy = 0;
        }
    }
    void add(ll i, ll pos, ll k) { // 单点修改
        if (tr[i].l == tr[i].r) {
            tr[i].sum += k;
            return;
        }
        pushdown(i);
        ll mid = (tr[i].l + tr[i].r) >> 1;
        if (pos <= mid) add(i << 1, pos, k);
        else add(i << 1 | 1, pos, k);
        tr[i].sum = tr[i << 1].sum + tr[i << 1 | 1].sum;
    }

    void modify(ll i, ll l, ll r, ll k) { // 区间修改
        if (tr[i].l >= l && tr[i].r <= r) {
            tr[i].sum += k * (tr[i].r - tr[i].l + 1);
            tr[i].lazy += k;
            return;
        }
        pushdown(i);
        ll mid = (tr[i].l + tr[i].r) >> 1;
        if (l <= mid) modify(i << 1, l, r, k);
        if (r > mid) modify(i << 1 | 1, l, r, k);
        tr[i].sum = tr[i << 1].sum + tr[i << 1 | 1].sum;
    }

    ll search(ll i, ll l, ll r) { // 区间查询
        if (tr[i].l >= l && tr[i].r <= r) return tr[i].sum;
        pushdown(i);
        ll mid = (tr[i].l + tr[i].r) >> 1;
        ll sum = 0;
        if (l <= mid) sum += search(i << 1, l, r);
        if (r > mid) sum += search(i << 1 | 1, l, r);
        return sum;
    }

    ll query(ll i, ll pos) { // 单点查询
        if (tr[i].l == tr[i].r) return tr[i].sum;
        pushdown(i);
        ll mid = (tr[i].l + tr[i].r) >> 1;
        if (pos <= mid) return query(i << 1, pos);
        else return query(i << 1 | 1, pos);
    }
} ST;

int main() {
    ll t = 1;
    while (t--) {
        cin >> n >> m;
        for (ll i = 1; i <= n; i++) cin >> a[i];
        ST.build(1, 1, n);
        for (ll i = 1; i <= m; i++) {
            cin >> l;
            if(l == 1) {
                cin >> x >> k;
                ST.add(1, x, k);
            } else if (l == 2) {
                cin >> x >> y >> k;
                ST.modify(1, x, y, k);
            } else if (l == 3) {
                cin >> x;
                cout << ST.query(1, x) << "\n";
            } else if (l == 4) {
                cin >> x >> y;
                cout << ST.search(1, x, y) << "\n";
            }
        }
    }
    return 0;
}

Tarjan

ll n, m;
ll dfn[N], dfn_maxx;//表示这个点在dfs时是第几个被搜到的
ll low[N];//表示这个点以及其子孙节点连的所有点中dfn最小的值
stack<ll> st;
ll id[N], id_maxx;//标记这个点位于的强连通分量
ll num[N];// 这个强连通分量中的点的数量
ll out[N];//每个强连通分量的出度
ll vis[N];//标记是否在栈中
ll head[N];//每个点连接的第一条边的索引
struct edge {//链式向前星记录的是一个点连接的所有边
    ll to;//目标节点
    ll nxt;//下一条边的索引
}e[M];
ll cnt;//边的总数,从1开始计数
void add_edge(ll u, ll v) {
    cnt ++;
    e[cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}
void tarjan(ll u) {
    low[u] = dfn[u] = ++ dfn_maxx;
    vis[u] = 1;
    st.push(u);
    for(ll i = head[u]; i; i = e[i].nxt) {
        ll v = e[i].to;
        if(dfn[v] != 0) {
            if(vis[v]) low[u] = min(low[u], dfn[v]);
        }
        else {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
    }
    //找到一个强连通分量的头节点
    if(dfn[u] == low[u]) {
        id[u] = ++ id_maxx;
        num[id_maxx] ++ ;
        vis[u] = 0;
        while(st.top() != u) {
            ll v = st.top(); st.pop();
            vis[v] = 0;
            id[v] = id_maxx;
            num[id_maxx] ++ ;
        }
        st.pop();
    }
}
void Solve() {
    cin >> n >> m;
    for(ll i = 1; i <= m; i ++ ) {
        ll u, v;
        cin >> u >> v;
        add_edge(u, v);
    }
    //tarjan找强连通分量
    for(ll i = 1; i <= n; i ++ ) {
        if(!dfn[i]) tarjan(i);
    }
    //计算强连通分量的出度
    for(ll i = 1; i <= n; i ++ ) {
        for(ll j = head[i]; j; j = e[j].nxt) {
            if(id[i] != id[e[j].to]) out[id[i]] ++ ;
        }
    }
    ll summ = 0;// 统计强连通分量的数量
    ll ans;
    for(ll i = 1; i <= id_maxx; i ++ ) {
        if(out[i] == 0) {summ ++ ; ans = num[i];}
    }
    if(summ == 1) cout << ans << "\n";
    else cout << "0" << "\n";
}

数学

排列组合

//费马小定理求逆元(质数)
const LL maxn(5050);
LL Jc[maxn]; 
void calJc()    //求maxn以内的数的阶乘
{
    Jc[0] = Jc[1] = 1;
    for(LL i = 2; i < maxn; i++)
        Jc[i] = Jc[i - 1] * i % q;
}
LL pow(LL a, LL n, LL p)    //快速幂 a^n % p
{
    LL ans = 1;
    while(n)
    {
        if(n & 1) ans = ans * a % q;
        a = a * a % q;
        n >>= 1;
    }
    return ans;
}
LL niYuan(LL a, LL b)   //费马小定理求逆元
{
    return pow(a, b - 2, b);
}
LL CC(LL a, LL b)   //计算C(a, b)
{
    if(a < b) return 0;
    return Jc[a] * niYuan(Jc[b], q) % q
        * niYuan(Jc[a - b], q) % q;
}

// 暴力求组合数
vector<vector<int>> comb(maxn + 1, vector<int>(maxn + 1, 0)); 
// 计算组合数,取模
void calculate_combinations() {
    for (int n = 0; n <= maxn; ++n) {
        for (int k = 0; k <= n; ++k) {
            if (k == 0 || k == n) {
                comb[n][k] = 1; // C(n, 0) = C(n, n) = 1
            } else {
                comb[n][k] = (comb[n - 1][k - 1] + comb[n - 1][k]) % q; // 递推关系式,取模
            }
        }
    }
}
// 查询组合数,取模
int get_combination(int n, int k) {
    if (k > n || k < 0) {
        return 0; // 如果 k 超过 n 或者小于 0,则返回 0
    }
    return comb[n][k];
}

卡特兰数

$$ H_n = \frac {\binom{2n}{n}}{n + 1}(n \geq 2) \\ H_n=\left\{ \begin{array}{rcl} \sum_{i = 1}^n H_{i - 1}H_{n - i} & & {n \geq 2}\\ 1 & & {n = 0,1} \end{array} \right. \\ H_n = \frac{H_{n - 1}(4n - 2)}{n + 1} \\ H_n = {\binom{2n}{n}} - {\binom{2n}{n - 1}} $$

SG

void SG() {
    for(ll i = 1; i <= 100; i ++ ) {
        for(ll j = 1; j <= i; j ++ ) {    
            set<ll> s;
            for(ll k = (i + 1) / 2; k < i; k ++ ) {
                s.insert(sg[{k, i - k}]);
            }
            for(ll k = (j + 1) / 2; k < j; k ++ ) {
                s.insert(sg[{k, j - k}]);
            }
            while(s.find(sg[{i, j}]) != s.end()) {
                sg[{i, j}] ++ ;
            }
        }
    }
}

图论

LCA(重链剖分)

// P3379 【模板】最近公共祖先(LCA)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll N = 500500;
vector<ll> g[N];
ll fa[N];  // 父节点
ll dep[N]; // 深度
ll top[N]; // 重链头节点
ll sz[N];  // 子树大小
ll son[N]; // 重子节点

void dfs1(ll u) { // 维护子树大小、节点深度、父节点、重子节点
    sz[u] = 1;
    dep[u] = dep[fa[u]] + 1;
    for(auto v : g[u]) {
        if(v == fa[u]) continue;
        fa[v] = u;
        dfs1(v);
        sz[u] += sz[v];
        if(sz[v] > sz[son[u]]) son[u] = v;
    }
}

void dfs2(ll u, ll h) { //维护重链头节点
    top[u] = h;
    if(son[u]) dfs2(son[u], h); 
    for(auto v : g[u]) {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

ll LCA(ll u, ll v) {
    while(top[u] != top[v]) {
        if(dep[top[u]] > dep[top[v]]) 
            u = fa[top[u]];
        else 
            v = fa[top[v]];
    }
    return dep[u] < dep[v] ? u : v;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    ll n, m, s; cin >> n >> m >> s;
    for(ll i = 1; i < n; i ++ ) {
        ll u, v; cin >> u >> v;
        g[u].push_back(v); 
        g[v].push_back(u);
    }
    dfs1(s); dfs2(s, s);
    for(ll i = 1; i <= m; i ++ ) {
        ll u, v; cin >> u >> v;
        cout << LCA(u, v) << "\n"; 
    }
    return 0;
}

LCA(dfs序 + ST)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 6;
vector<ll> g[N];
ll dfn[N];       // dfs时间戳
ll st[N][3];    // st数组,前一个参数为dfn[u], 后一个参数为fa[u] 
ll dn;           // 时间戳

void dfs(ll u, ll fa) {  // 维护时间戳
    st[dfn[u] = ++ dn][0] = fa;
    for(auto v : g[u]) {
        if(v != fa) dfs(v, u);
    }
}

ll get(ll x, ll y) {   // 求最小的时间戳的节点
    return dfn[x] < dfn[y] ? x : y;
}

void ST(ll n) {         // 构造st数组
    for(ll j = 1; j <= __lg(n); j ++ ) {
        for(ll i = 1; i <= n - (1 << j) + 1; i ++ ) {
            st[i][j] = get(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
}

ll LCA(ll u, ll v) {
    if(u == v) return u;
    if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
    ll k = __lg(v - u ++ );
    ll d = v - (1 << k) + 1;
    return get(st[u][k], st[d][k]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    ll n, m, s; cin >> n >> m >> s;
    for(ll i = 1; i < n; i ++ ) {
        ll u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(s, 0); ST(n);
    for(ll i = 1; i <= m; i ++ ) {
        ll u, v; cin >> u >> v;
        cout << LCA(u, v) << "\n";
    }
    return 0;
}

网络流

EK 时间复杂度$O(nm^2)$

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAX = 1210;
const int inf = 0x3f3f3f3f;
ll graph[MAX][MAX];
ll n, m, st, ed;
ll ans;
ll pre[MAX];
bool vis[MAX];    
ll l[MAX];

bool bfs() {
    memset(pre, -1, sizeof(pre));
    memset(vis, 0, sizeof(vis));
    ll hh = 0, tt = 0;
    pre[st] = st;
    vis[st] = 1;
    l[ ++ tt] = st;

    while(hh < tt) {
        ll pos = l[ ++ hh];
        for(ll i = 1; i <= n; i ++ ) {
            if(!vis[i] && graph[pos][i] > 0) {
                vis[i] = 1;
                pre[i] = pos;
                if(i == ed) return true;
                l[ ++ tt] = i;
            }
        }
    }
    return false;
}

void EK() {    
    while(bfs()) {
        ll w = inf;
        for(ll i = ed; i != st; i = pre[i]) w = min(w, graph[pre[i]][i]);
        for(ll i = ed; i != st; i = pre[i]) {
            graph[pre[i]][i] -= w;
            graph[i][pre[i]] += w;
        }
        ans += w;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n >> m >> st >> ed;
    ll x, y, z;
    for(ll i = 1; i <= m; i ++ ) {
        cin >> x >> y >> z;
        if(graph[x][y] == 0) graph[x][y] = z;
        else graph[x][y] += z;
    }
    EK();
    cout << ans << "\n";
    return 0;
}

Dinic 时间复杂度$O(n^2m)$

#include <bits/stdc++.h>
using namespace std;
const long long inf=2005020600;
int n,m,s,t,u,v;
long long w,ans,dis[520010];
int tot=1,now[520010],head[520010]; 

struct node {
    int to,net;
    long long val;
} e[520010];

inline void add(int u,int v,long long w) {
    e[++tot].to=v;
    e[tot].val=w;
    e[tot].net=head[u];
    head[u]=tot;
    
    e[++tot].to=u;
    e[tot].val=0;
    e[tot].net=head[v];
    head[v]=tot;
}

inline int bfs() {  //在惨量网络中构造分层图 
    for(register int i=1;i<=n;i++) dis[i]=inf;
    queue<int> q;
    q.push(s);
    dis[s]=0;
    now[s]=head[s];
    while(!q.empty()) {
        int x=q.front();
        q.pop();
        for(register int i=head[x];i;i=e[i].net) {
            int v=e[i].to;
            if(e[i].val>0&&dis[v]==inf) {
                q.push(v);
                now[v]=head[v];
                dis[v]=dis[x]+1;
                if(v==t) return 1;
            }
        }
    }
    return 0;
}

inline int dfs(int x,long long sum) {  //sum是整条增广路对最大流的贡献
    if(x==t) return sum;
    long long k,res=0;  //k是当前最小的剩余容量 
    for(register int i=now[x];i&&sum;i=e[i].net) {
        now[x]=i;  //当前弧优化 
        int v=e[i].to;
        if(e[i].val>0&&(dis[v]==dis[x]+1)) {
            k=dfs(v,min(sum,e[i].val));
            if(k==0) dis[v]=inf;  //剪枝,去掉增广完毕的点 
            e[i].val-=k;
            e[i^1].val+=k;
            res+=k;  //res表示经过该点的所有流量和(相当于流出的总量) 
            sum-=k;  //sum表示经过该点的剩余流量 
        }
    }
    return res;
}
int main() {
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(register int i=1;i<=m;i++) {
        scanf("%d%d%lld",&u,&v,&w);
        add(u,v,w);
    }
    while(bfs()) {
        ans+=dfs(s,inf);  //流量守恒(流入=流出) 
    }
    printf("%lld",ans);
    return 0;
}