退役了退役了,把自己的板子放一下,需要的自取

矩阵算法

扫描线线段树

单调栈

扫描线

int a[N][M], n, m;
int l[N][N], r[N][N], h[N][N];//向左,右,上最远

int read() {
    char c = getchar();
    while (c != '1' && c != '0')c = getchar();
    return c == '1'; //返回1代表障碍
}

//切记mr=m+1!!!!
//遇到是障碍 ml=0,mr=m+1!!!!

int main() {
    int ans = 0;
    scc(n, m);
    fir(i, 1, m) r[0][i] = m + 1;
    fir(i, 1, n) {
        int ml = 0, mr = m + 1;
        fir(j, 1, m) {
            a[i][j] = read();
            if (a[i][j]) ml = j, l[i][j] = 0, h[i][j] = 0;
            else l[i][j] = max(l[i - 1][j], ml), h[i][j] = h[i - 1][j] + 1;
        }
        fri(j, m, 1)
            if (a[i][j]) mr = j, r[i][j] = m + 1;
            else r[i][j] = min(r[i - 1][j], mr),
                ans = max(ans, h[i][j] * (r[i][j] - l[i][j] - 1);
    }
    printf("%d", ans);
    return 0;
}

障碍法

int n, m, s, ans = 0;
PII a[N];

//顺序不能改
inline bool work(int &up, int &down, int i, int j, int v)
{
    if (v * (down - up) <= ans) return 1;
    umax(ans, (down - up) * (abs(a[j].x - a[i].x)));
    if (a[j].y<up || a[j].y>down)return 0;
    //尽管不在上下界内,也要先算在返回,防止这个点不卡上下边界,但是最以后一个点而没算
    if (a[j].y == a[i].y)return 1;
    if (a[j].y < a[i].y)up = a[j].y;
    else down = a[j].y;
    return 0;
}

int main() {
    sccc(n, m, s); //n*m网格, s个障碍, 复杂度只和s有关(O(s^2))
    fir(i, 1, s) scc(a[i].fi, a[i].se);
    a[++s] = { 0, 0 }; a[++s] = { n, 0 };
    a[++s] = { 0, m }; a[++s] = { n, m };
    sort(a + 1, a + 1 + s);
    fir(i, 1, s) {
        RE int up = 0, down = m, v = n - a[i].fi;
        fir(j, i + 1, s) if (work(up, down, i, j, v)) break;
        up = 0, down = m, v = a[i].fi;
        fri(j, i - 1, 1) if (work(up, down, i, j, v)) break;
    }
    sort(a + 1, a + 1 + s, [&](PII x, PII y) {
        return x.se != y.se ? x.se < y.se : x.fi < y.fi;
    });
    fir(i, 1, s - 1) umax(ans,  m * (a[i + 1].se - a[i].se));
    printf("%d", ans);
}

数据结构

并查集

带权

int n, f[N], dep[N], sz[N];
int find(int x) {
    if (x == f[x]) return x;
    if (f[x] == f[f[x]]) return f[x];
    int fa = find(f[x]); dep[x] += dep[f[x]] - 1;
    return f[x] = fa;
}
void unit(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) dep[y] = sz[x] + 1, sz[f[y] = x] += sz[y];
}

分块

解决出现次数

#pragma GCC optimize(2)
const int N = 1e5 + 5, M = pow(N, 1.0 / 3) + 5;
int n, m, k, cnt[M][N], val[M][M], t, len, a[N], id[N];
vector<int> c;
void init() {
    for (int i = 1; i <= n; ++i) cin >> a[i], c.push_back(a[i]);
    sort(all(c)); c.erase(unique(all(c)), c.end());
    t = pow(n, 1.0 / 3); len = (n - 1) / t + 1; t = (n - 1) / len + 1;
    rep(i, 1, n) a[i]=lower_bound(all(c),a[i])-c.begin(), id[i]=(i-1)/len+1;
    for (int i = t, res = 0; i; --i, res = 0) {
        for (int j = min(n, i * len); j; --j) {
            ++cnt[i][a[j]];
            if ((cnt[i][a[j]] & 1) && cnt[i][a[j]] - 1) --res;
            else if (cnt[i][a[j]] - 1) ++res;
            if (j % len == 1) val[id[j]][i] = res;
        }
    }
}
int work(int l, int L, int R, int r) {    int ans = val[id[L]][id[R]];
    for (int i = l; i < L; ++i) {
        ++cnt[id[L] - 1][a[i]];
        if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]] & 1) --ans;
        else if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]]) ++ans;
    }
    for (int i = r; i > R; --i) {
        --cnt[id[R]][a[i]];
        if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]] & 1) --ans;
        else if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]]) ++ans;
    }
    for (int i = l; i < L; ++i) --cnt[id[L] - 1][a[i]];
    for (int i = r; i > R; --i) ++cnt[id[R]][a[i]];
    return ans;
}
int main() {
    IOS; cin >> n >> k >> m; init();
    for (int i = 0, ls = 0; i < m; ++i) {
        int l, r; cin >> l >> r; l = (l + ls) % n + 1, r = (r + ls) % n + 1;
        if (l > r) swap(l, r);
        cout << (ls = work((id[l] - 1) * len + 1, l, r, min(id[r] * len, n))) << '\n';
    }
    return 0;
}

树状数组

区改单查

int a[N], c[N];
void add(int x, int k) { for (; x <= n; x += -x & x) c[x] += k; }
void add(int l, int r, int k) { add(l, k); add(r + 1, -k); }
int ask(int x) { int ans = 0; for (; x; x -= x & x) ans += c[x]; return ans; } //+a[x];

区查区改

int c[2][N], a[N], sum[N];
void add(int x,ll k) {for(int i=x;i<=n;i+=-i&i) c[0][i] += k, c[1][i] += x * k; }
void add(int l, int r, ll k) { add(l, k); add(r + 1, -k); }
ll ask(int x) {
    ll p = 0, q = 0, f = x + 1;
    for (; x; x -= -x & x) p += c[0][x], q += c[1][x];
    return p * f - q;
}
ll ask(int l, int r) { return ask(r) - ask(l - 1); }//+sum[r] - sum[l - 1]

二维区改单查

ll c[N][M], a[N][M];
void add(int x, int y, ll k){
    for (int t = y; x <= n; x += -x & x, y = t)
        for (; y <= m; y += -y & y) c[x][y] += k;
}
void add(int x1, int y1, int x2, int y2, ll k) { //左上角, 右下角
    add(x1, y1, k); add(x2 + 1, y2 + 1, k);
    add(x1, y2 + 1, -k); add(x2 + 1, y1, -k);
}
ll ask(int x, int y) {
    ll ans = 0;
    for (int t = y; x <= n; x -= -x & x, y = t)
        for (; y <= m; y -= -y & y) ans += c[x][y];
    return ans;
}

二维区查区改

ll c[4][N][M], a[N][M], sum[N][M];
void add(int x, int y, ll k) {
    for (int i = x; i <= n; i += -i & i)
        for (int j = y; j <= m; j += -j & j) {
            c[0][i][j] += k;
            c[1][i][j] += k * x;
            c[2][i][j] += k * y;
            c[3][i][j] += k * x * y;
        }
}
void add(int x1, int y1, int x2, int y2, ll k) {
    add(x1, y1, k); add(x2 + 1, y2 + 1, k);
    add(x1, y2 + 1, -k); add(x2 + 1, y1, -k);
}
ll ask(int x, int y) {
    ll p = 0, q = 0, r = 0, t = 0;
    for (int i = x; i; i -= -i & i)
        for (int j = y; j; j -= -j & j)
            p += c[0][i][j], q += c[1][i][j], r += c[2][i][j], t += c[3][i][j];
    return p * (x + 1) * (y + 1) - q * (y + 1) - r * (x + 1) + t;
}
ll ask(int x1, int y1, int x2, int y2) {
    return ask(x2, y2) - ask(x1 - 1, y2) - ask(x2, y1 - 1) + ask(x1 - 1, y1 - 1);
}

线段树

基本(区间和)

struct BitTree {
    struct node { int l, r, val, tag, len; } tr[N << 2];
    void push_up(int rt) { tr[rt].val = tr[rt<<1].val + tr[rt<<1|1].val; }
    void push_down(int rt) {
        tr[rt << 1].tag += tr[rt].tag, tr[rt << 1 | 1].tag += tr[rt].tag;
        tr[rt << 1].val += tr[rt << 1].len * tr[rt].tag;
        tr[rt << 1 | 1].val += tr[rt << 1 | 1].len * tr[rt].tag;
        tr[rt].tag = 0;
    }
    void build(int rt, int l, int r, int* a) {
        tr[rt] = {l, r, 0, 0, r - l + 1};
        if (l == r) { tr[rt].val = a[l]; return; }
        int mid = l + r >> 1;
        build(rt << 1, l, mid, a); build(rt << 1 | 1, mid + 1, r, a);
        push_up(rt);
    }
    void change(int rt, int l, int r, int k) {
        if (r < l) return;
        if (tr[rt].l >= l && tr[rt].r <= r) {
            tr[rt].val += tr[rt].len * k;
            tr[rt].tag += k; return;
        } push_down(rt);
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (l <= mid) change(rt << 1, l, r, k);
        if (r > mid) change(rt << 1 | 1, l, r, k);
        push_up(rt);
    }
    ll ask(int rt, int l, int r) {
        if (r < l) return 0;
        if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
        push_down(rt);
        int mid = tr[rt].l + tr[rt].r >> 1;
        ll ans = l <= mid ? ask(rt << 1, l, r) : 0;
        if (r > mid) ans += ask(rt << 1 | 1, l, r);
        push_up(rt); return ans;
    }
} bit;

扫描线面积(计算覆盖k次的面积)

struct BIT { //总的覆盖k次以上的长度为tr[1].val[k - 1]
    static const int N = 2e3 + 5;
    struct Node {
                int l, r, len, val[3], cnt;//val[i]代表扫描i-1次的线段长度,cnt为覆盖次数
        int& operator [](int k) { return val[k]; }
    } tr[N << 2];
    void push_up(int rt) {
        rep (i, 0, 2)//rep(i, 0, k - 1)
            if (tr[rt].cnt > i) tr[rt][i] = tr[rt].len;
            else if (tr[rt].l + 1 == tr[rt].r) tr[rt][i] = 0;
            else tr[rt][i] = tr[rt<<1][i - tr[rt].cnt] + tr[rt<<1|1][i - tr[rt].cnt];
    }
    void build(int rt, int l, int r, VI& a) {//a为离散化的x vector
        tr[rt].l = l, tr[rt].r = r, tr[rt].len = a[r] - a[l];
        tr[rt][0] = tr[rt][1] = tr[rt][2] = tr[rt].cnt = 0;
        if (l + 1 == r) return;
        int mid = l + r >> 1;
        build(rt << 1, l, mid, a); build(rt << 1 | 1, mid, r, a);
    }
    void change(int rt, int l, int r, int k) {
        if (tr[rt].l >= l && tr[rt].r <= r) { tr[rt].cnt += k; push_up(rt); return; }
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (mid > l) change(rt << 1, l, r, k);
        if (mid < r) change(rt << 1 | 1, l, r, k);
        push_up(rt);
    }
    int ask(int rt, int l, int r, int k) {//覆盖k次以上的长度和
        if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt][k];
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (r <= mid) return ask(rt << 1, l, r, k);
        if (l >= mid) return ask(rt << 1 | 1, l, r, k);
        return ask(rt << 1, l, r, k) + ask(tr << 1 | 1, l, r, k);
    }
} bit;

扫描线周长

struct BIT {
    static const int N = 1e4 + 5;
    struct node { int l, r, len, cnt, tag, val[2]; } tr[N << 2];
    void push(int rt) {
        if (tr[rt].tag) tr[rt].val[0] = tr[rt].val[1] = tr[rt].len, tr[rt].cnt = 1;
        else if (tr[rt].l + 1 == tr[rt].r)
            tr[rt].val[0] = tr[rt].val[1] = 0, tr[rt].cnt = 0;
        else {
            tr[rt].cnt = tr[rt << 1].cnt + tr[rt << 1 | 1].cnt -
                (tr[rt << 1].val[1] && tr[rt << 1 | 1].val[0] ? 1 : 0);
            tr[rt].val[0] = tr[rt << 1].val[0] +
                (tr[rt << 1].val[0] ^ tr[rt << 1].len ? 0 : tr[rt << 1 | 1].val[0]);
            tr[rt].val[1] = tr[rt << 1 | 1].val[1] +
                (tr[rt << 1 | 1].val[1] ^ tr[rt << 1 | 1].len ? 0 : tr[rt << 1].val[1]);
         }
    }
    void build(int rt, int l, int r, vector<int>& a) {
        tr[rt].l = l, tr[rt].r = r, tr[rt].len = a[r] - a[l];
        if (l + 1 == r) return;
        int mid = l + r >> 1; build(rt << 1, l, mid, a); build(rt << 1 | 1, mid, r, a);
    }
    void change(int rt, int l, int r, int k) {
        if (tr[rt].l >= l && tr[rt].r <= r) { tr[rt].tag += k; push(rt); return; }
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (l < mid) change(rt << 1, l, r, k);
        if (r > mid) change(rt << 1 | 1, l, r, k);
        push(rt);
    }
} bit;
struct line { int y, a, b, k; } a[10005];
int n, x[10000], y[10000];
long long work(int x[], int y[]) {
    vector<int> c; c.push_back(-2e9);
    for (int i = 0; i < n; ++i) {
        a[i << 1] = { y[i << 1], x[i << 1], x[i << 1 | 1], 1 };
        a[i << 1 | 1] = { y[i << 1 | 1], x[i << 1], x[i << 1 | 1], -1 };
        c.push_back(x[i << 1]); c.push_back(x[i << 1 | 1]);
    }
    sort(c.begin(), c.end()); c.erase(unique(c.begin(), c.end()), c.end());
    sort(a, a + n * 2, [](line& a, line& b){return a.y^b.y?a.y<b.y:a.k>b.k;});
    long long ans = 0; bit.build(1, 1, c.size() - 1, c);
    bit.change(1, lower_bound(c.begin(), c.end(), a[0].a) - c.begin(),
        lower_bound(c.begin(), c.end(), a[0].b) - c.begin(), a[0].k);
    for (int i = 1; i < n << 1; ++i) {
        ans += (long long)bit.tr[1].cnt * (a[i].y - a[i - 1].y) << 1;
        bit.change(1, lower_bound(c.begin(), c.end(), a[i].a) - c.begin(),
            lower_bound(c.begin(), c.end(), a[i].b) - c.begin(), a[i].k);
    }
    return ans;
}
int main() {
    cin >> n;
    for (int i = 0; i < n; ++i) cin>>x[i<<1]>>y[i<<1]>>x[i<<1|1]>>y[i<<1|1];
    cout << work(x, y) + work(y, x);
    return 0;
}

主席树

可持续线段树

struct CHT {
    static const int N = 1e5 + 5;
    struct Node { int l, r; ll val, tag; } tr[N * 25];
    int rt[N], tot; // 不可push_up/down
    void build(int& rt, int l, int r, ll* a) {
        tr[rt = ++tot] = { 0, 0, 0, 0 };
        if (l == r) { tr[rt].val = a[l]; return; }
        int mid = l + r >> 1;
        build(tr[rt].l, l, mid, a); build(tr[rt].r, mid + 1, r, a);
        tr[rt].val = tr[tr[rt].l].val + tr[tr[rt].r].val
    }
    void change(int& x, int y, int pl, int pr, int l, int r, ll k) {
        tr[x = ++tot] = tr[y];
        tr[x].val += (min(r, pr) - max(pl, l) + 1) * k;
        if (l <= pl && pr <= r) { tr[x].tag += k; return; }
        int mid = pl + pr >> 1;
        if (l <= mid) change(tr[x].l, tr[y].l, pl, mid, l, r, k);
        if (r > mid) change(tr[x].r, tr[y].r, mid + 1, pr, l, r, k);
    }
    ll ask(int rt, int pl, int pr, int l, int r) {
        if (l <= pl && pr <= r) return tr[rt].val;
        ll ans = (min(pr, r) - max(pl, l) + 1) * tr[rt].tag;
        int mid = pl + pr >> 1;
        if (mid >= l) ans += ask(tr[rt].l, pl, mid, l, r);
        if (mid < r) ans += ask(tr[rt].r, mid + 1, pr, l, r);
        return ans;
    }
} T;

静态

struct CHT {
    struct node { int val, lson, rson; } tr[20 * N]; //log2(sz)*n,sz值域
    int root[N], tot;
    /*void push_up(int rt) { tr[rt].val = tr[tr[rt].l].val + tr[tr[rt].r].val; }
    //这样build tr 要 (log2(n) + 4) * n
    void build(int &rt, int l, int r, int countofa[]) {
        rt = ++tot; tr[rt].l = l, tr[rt].r = r;
        if (l == r) { tr[rt].val = countofa[l]; return; }
        int mid = l + r >> 1;
        build(tr[rt].lson, l, mid, countofa);
        build(tr[rt].rson, mid + 1, r, countofa);
        push_up(rt);
    } */
    void update(int &x, int y, int l, int r, int k, int cnt) {
        tr[x = ++tot] = tr[y]; tr[x].val += cnt;
        if (l == r) return;
        int mid = l + r >> 1;
        if (k <= mid) update(tr[x].lson, tr[y].lson, l, mid, k, cnt);
        else update(tr[x].rson, tr[y].rson, mid + 1, r, k, cnt);
    }
    int ask(int x, int y, int l, int r, int k) {
        if (l == r) return l;
        int mid = l + r >> 1, val = tr[tr[x].lson].val - tr[tr[y].lson].val;
        if (val >= k) return ask(tr[x].lson, tr[y].lson, l, mid, k);
        else return ask(tr[x].rson, tr[y].rson, mid + 1, r, k - val);
    }
};

动态

const int N = 1e5 + 5;
struct CHT {
    struct node { int val, lson, rson; } tr[N * 300]; //log2(sz)*log2(n)*n
    int root[N], tot, lsc, rsc, sz, n; // n为数组大小,sc为值域
    int ls[20], rs[20]; //log(n)
    void build(int n, int* a, int sz) {
        this->sz = sz; this->n = n;
        for (int i = 1; i <= n; ++i) change(i, a[i], 1);
    }
    void update(int &rt, int l, int r, int k, int cnt) {
        if (!rt) rt = ++tot;
        tr[rt].val += cnt;
        if (l == r) return;
        int mid = l + r >> 1;
        if (k <= mid) update(tr[rt].lson, l, mid, k, cnt);
        else update(tr[rt].rson, mid + 1, r, k, cnt);
    }
    void change(int x, int k, int cnt) { //n为最大区间长度
        for (int i = x; i <= n; i += i & -i) update(root[i], 1, sz, k, cnt);
    }
    int ask(int l, int r, int k) {
        if (l == r) return l;
        int sum = 0;
        for (int i = 1; i <= lsc; ++i) sum -= tr[tr[ls[i]].lson].val;
        for (int i = 1; i <= rsc; ++i) sum += tr[tr[rs[i]].lson].val;
        int mid = l + r >> 1;
        if (sum >= k) {
            for (int i = 1; i <= lsc; ++i) ls[i] = tr[ls[i]].lson;
            for (int i = 1; i <= rsc; ++i) rs[i] = tr[rs[i]].lson;
            return ask(l, mid, k);
        } else {
            for (int i = 1; i <= lsc; ++i) ls[i] = tr[ls[i]].rson;
            for (int i = 1; i <= rsc; ++i) rs[i] = tr[rs[i]].rson;
            return ask(mid + 1, r, k - sum);
        }
    }
    int preask(int l, int r, int k) {
        lsc = rsc = 0;
        for (int i = l; i; i -= -i & i) ls[++lsc] = root[i];
        for (int i = r; i; i -= -i & i) rs[++rsc] = root[i];
        return ask(1, sz, k);
    }
} bit;
cout << c[bit.preask(q[i].l - 1, q[i].r, q[i].k)] << '\n';
bit.change(q[i].l, a[q[i].l], -1);
a[q[i].l] = lower_bound(all(c), q[i].k) - c.begin();
bit.change(q[i].l, a[q[i].l], 1);

树上动态第k大,通过dfs序维护

struct CHT {
    static const int M = 6e7;
    struct node {
        int val, lson, rson; /*int l, r; 值域*/
        node (int Val = 0, int Ls = 0, int Rs = 0)
            : val(Val), lson(Ls), rson(Rs){}
    } tr[N* 520]; //log2(sz)*log2(sz)*n
    int root[N], tot, qsc, qtc, quc, qwc, sz, n; // n为数组大小,sc为值域
    int qs[15], qt[15], qu[15], qw[15]; //log(n)
    void build(int n, int sz, int *a, PII *dfn) {
        this->sz = sz; this->n = n;
        for (int i = 1; i <= n; ++i)
            change(dfn[i].fi, a[i], 1), change(dfn[i].se + 1, a[i], -1);
    }
    void update(int &rt, int l, int r, int k, int cnt) {
        if (!rt) rt = ++tot;
        tr[rt].val += cnt;
        if (l == r) return;
        int mid = l + r >> 1;
        if (k <= mid) update(tr[rt].lson, l, mid, k, cnt);
        else update(tr[rt].rson, mid + 1, r, k, cnt);
    }
    void change(int x, int k, int cnt) { //n为最大区间长度
        for (int i = x; i <= n; i += i & -i) update(root[i], 0, sz, k, cnt);
    }
    int ask(int l, int r, int k) {
        if (l == r) return l;
        int sum = 0;
        for (int i = 1; i <= qsc; ++i) sum += tr[tr[qs[i]].lson].val;
        for (int i = 1; i <= qtc; ++i) sum += tr[tr[qt[i]].lson].val;
        for (int i = 1; i <= quc; ++i) sum -= tr[tr[qu[i]].lson].val;
        for (int i = 1; i <= qwc; ++i) sum -= tr[tr[qw[i]].lson].val;
        int mid = l + r >> 1;
        if (sum >= k) {
            for (int i = 1; i <= qsc; ++i) qs[i] = tr[qs[i]].lson;
            for (int i = 1; i <= qtc; ++i) qt[i] = tr[qt[i]].lson;
            for (int i = 1; i <= quc; ++i) qu[i] = tr[qu[i]].lson;
            for (int i = 1; i <= qwc; ++i) qw[i] = tr[qw[i]].lson;
            return ask(l, mid, k);
        } else {
            for (int i = 1; i <= qsc; ++i) qs[i] = tr[qs[i]].rson;
            for (int i = 1; i <= qtc; ++i) qt[i] = tr[qt[i]].rson;
            for (int i = 1; i <= quc; ++i) qu[i] = tr[qu[i]].rson;
            for (int i = 1; i <= qwc; ++i) qw[i] = tr[qw[i]].rson;
            return ask(mid + 1, r, k - sum);
        }
    }
    int preask(int s, int t, int u, int w, int k) {
        qtc = qsc = quc = qwc = 0;
        for (int i = s; i; i -= -i & i) qs[++qsc] = root[i];
        for (int i = t; i; i -= -i & i) qt[++qtc] = root[i];
        for (int i = u; i; i -= -i & i) qu[++quc] = root[i];
        for (int i = w; i; i -= -i & i) qw[++qwc] = root[i];
        return ask(0, sz, k);
    }
} bit;
int n, m, _, k;
int h[N], ne[N << 1], to[N << 1], tot;
int op[N], x[N], y[N], a[N], df;
PII dfn[N];
void add(int u, int v) { ne[++tot] = h[u]; to[h[u] = tot] = v;}
void dfs(int x, int f) {
    dfn[x].fi = ++df;
    for (int i = h[x]; i; i = ne[i]) {
        int y = to[i];
        if (y == f) continue;
        dfs(y, x);
    } dfn[x].se = df;
}
int main() {//op==0,表a[x]修改为y,其他表示求(x,y)简单路径第op大
    IOS; cin >> n >> m; VI c;
    rep (i, 1, n) cin >> a[i], c.pb(a[i]);
    rep (i, 2, n) {
        int u, v; cin >> u >> v;
        add(u, v); add(v, u);
    }
    rep (i, 1, m) {
        cin >> op[i] >> x[i] >> y[i];
        if (!op[i]) c.pb(y[i]);
    }
    sort(all(c)); c.erase(unique(all(c)), c.end());
    rep (i, 1, n) a[i] = lower_bound(all(c), a[i]) - c.begin();
    rep (i, 1, m) if (!op[i]) y[i] = lower_bound(all(c), y[i]) - c.begin();
    ST.init(n, h, ne, to); ST.bfs(1);//st表,求lca的轮子
    dfs(1, 0); bit.build(n, c.size(), a, dfn);
    rep (i, 1, m) {
        if (!op[i]) {
            bit.change(dfn[x[i]].fi, a[x[i]], -1);
            bit.change(dfn[x[i]].se + 1, a[x[i]], 1);
            a[x[i]] = y[i];
            bit.change(dfn[x[i]].fi, a[x[i]], 1);
            bit.change(dfn[x[i]].se + 1, a[x[i]], -1);
        } else {
            int k = ST.dist(x[i], y[i]) + 2 - op[i], lca = ST.lca(x[i], y[i]);
            if (k <= 0) cout << "invalid request!\n";
            else cout << c[bit.preask(dfn[x[i]].fi, dfn[y[i]].fi,
                dfn[lca].fi, dfn[ST.f[lca][0]].fi, k)] << '\n';
        }
    }
    return 0;
}

可持续化01trie

1、在序列末尾添加一个数 x。
2、找到一个位置 p,满足l≤p≤r,a[p~N]异或和 xor x 最大,输出这个最大值。
即 a[1]~a[n] 异或 (l-1r-1)a[1]a[i]与x的异或和最大

struct SustainableTrie {
    const int N = 2e4 + 5, M = 31;
    struct node {
        int son[2], cnt;
        int& operator [](const int x) { return son[x]; }
    } tr[N * M];
    int rt[N], tot;
    void init() {
        for (int i = 1; i <= tot; ++i) rt[i] = 0; tot = 0;
        for (int i = 0; i < M; ++i) tr[0][i] = 0;
    }
    void insert(int& x, int y, int k) {
        int p = x = ++tot; tr[p] = tr[y];
        for (int i = M - 1; ~i; --i) {
            int ch = k >> i & 1;
            tr[p = tr[p][ch] = ++tot] = tr[y = tr[y][ch]];
            ++tr[p].cnt;
        }
    }
    int ask(int x, int y, int k) {
        int ans = 0;
        for (int i = M - 1; ~i; --i) {
            int ch = k >> i & 1;
            if (tr[tr[x][!ch]].cnt - tr[tr[y][!ch]].cnt)
                ans ^= 1ll << i, ch = !ch;
            x = tr[x][ch], y = tr[y][ch];
        } return ans;
    }
} trie;
int n, m, _, k, s;
int main() {
    IOS; cin >> n >> m; k = 1; trie.insert(trie.rt[k], trie.rt[0], 0);
    rep (i, 1, n) cin >> _, ++k, trie.insert(trie.rt[k], trie.rt[k - 1], s ^= _);
    rep (i, 1, m) {
        char op[2]; cin >> op;
        if (op[0] == 'A') cin>>_,++k,trie.insert(trie.rt[k],trie.rt[k - 1],s^=_);
        else {
            int l, r, x; cin >> l >> r >> x;
            cout << trie.ask(trie.rt[r], trie.rt[l - 1], x ^ s) << '\n';
        }
    } return 0;
}
void work(int x) {
    int tail = 0; dis[x] = { 0, 0 }; q[++tail] = x; v[f[x] = x] = 1;
    /*for (auto& y : h[x]) if (!v[y.first] && y.second <= k)
        dis[q[++tail]=y.first]={y.second,1},dfs(y.first,x,f[y.first]=y.first,tail);
    sort(q + 1, q + tail + 1, [&](int a, int b) { return dis[a] < dis[b]; });
    for (int l = 1, r = 1; l <= tail; ++l) {
        while (l < r - 1 && dis[q[l]].first + dis[q[r - 1]].first >= k) --r;
        while (r < tail && dis[q[l]].first + dis[q[r]].first < k) ++r;
        for (; r <= tail && dis[q[l]].first + dis[q[r]].first == k; ++r)
            if (f[q[l]] ^ f[q[r]]) ans = min(ans, dis[q[l]].second + dis[q[r]].second);
    }遍历完孩子在计算*/
    unordered_map<int, int> st; st[0] = 0;//st可换成桶,循环完把更新过的位置重新设max即可
    for (auto& y : h[x]) if (!v[y.first] && y.second <= k) {
        dis[q[++tail] = y.first] = { y.second, 1 }; q[tail = 1] = y.first;
        dfs(y.first, x, f[y.first] = y.first, tail);
        for (int i = 1; i <= tail; ++i) if (st.count(k - dis[q[i]].first))
            ans = min(ans, st[k - dis[q[i]].first] + dis[q[i]].second);
        for (int i = 1; i <= tail; ++i) {
            auto it = st.find(dis[q[i]].first);
            if (it == st.end()) st.insert(dis[q[i]]);
            else it->second = min(it->second, dis[q[i]].second);
        }
    } //直接一边遍历一便计算, 省去f[]数组
    gravity(x, -1, n); mxsz = n;
    for(auto&y:h[x])if(!v[y.first])gravity(y.first,-1,sz[y.first]),work(gra),mxsz=n;
}
int main() {
    IOS; cin >> n >> k; mxsz = ans = n;
    for (int i = 1; i < n; ++i) {
        int u, v, c; cin >> u >> v >> c;
        h[u].push_back({ v, c }); h[v].push_back({ u, c });
    }
    gravity(0, -1, n); work(gra); cout << (ans ^ n ? ans : -1);
    return 0;
}

点分治

1.先找当前子树的重心, 别走出当前子树
2.在把重心当成当前子树的根, 开始计算
3.对当前子树根节点(重心)的儿子节点进行1~3

带权树, 求简单路径长度为k, 所包含最少边的数量

#pragma GCC optimize(2)
int n, k, gra, sz[N], mxsz, q[N], f[N], ans;
bool v[N];
pair<int, int> dis[N];
vector<pair<int, int>> h[N];
void gravity(int x, int fa, int s) {
    sz[x] = 1; int mx = 0;
    for (auto& y : h[x]) if (y.first != fa && !v[y.first])
        gravity(y.first, x, s), sz[x] += sz[y.first], mx = max(mx, sz[y.first]);
    if ((mx = max(mx, s - sz[x])) < mxsz) gra = x, mxsz = mx;
}
void dfs(int x, int fa, int id, int& tail) {
    for (auto& y : h[x]) if (y.first != fa && !v[y.first]) {
        f[y.first] = id; dis[q[++tail] = y.first].second = dis[x].second + 1;
        dis[y.first].first = dis[x].first + y.second;
        if (dis[y.first].first < k) dfs(y.first, x, id, tail);
    }
}

cdq 分治

三维偏序

第一行包含两个整数n和m,在刚开始时,Ayu已经知道有n个点可能埋着天使玩偶,接下来Ayu要进行m次操作。
接下来n行,每行两个非负整数xi,yi,表示初始n个点的坐标。
再接下来m行,每行三个非负整数 t,x,y 。
如果t=1,表示Ayu又回忆起了一个可能埋着玩偶的点(x,y)。
如果t=2,表示Ayu询问如果她在坐标(x,y),那么在已经回忆出的点里,离她最近的那个点有多远(曼哈顿)。

这题异常卡常, 开氧气, 最好再来快读
直接拆只考虑 \(|x-x_i|+|y-y_i|=(x+y)-(x_i+y_i)\)
旋转四次,cdq分治即可
直接拿时间轴当一维(直接有序)
二维偏序 x轴, 树状维护 y轴信息(还不用离散化)
记得树状恢复的技巧

#pragma GCC optimize(2)
template<class T1, class T2> bool umin(T1& a, T2 b) { return a > b?(a = b, true):false; }
template<class T1, class T2> bool umax(T1& a, T2 b) { return a < b?(a = b, true):false; }
template<class T> void clear(T& a) { T().swap(a); }
const int N = 5e5 + 5, M = 1e6 + 5;
int n, m, _, k;
struct node { int op, x, y, t; } q[N << 1], a[N << 1], b[N << 1];
int tot, ans[N << 1], c[M], mxx, mxy;
void add(int x, int k) { for (; x <= mxy; x += -x & x) umax(c[x], k); }
int ask(int x){int ans=0;for(;x;x-=-x&x) umax(ans,c[x]); return ans?ans:-1e7; }
void cl(int x) { for (; x <= mxy; x += -x & x) c[x] = 0; }
void init(int k) {
    int x = 0, y = 0; tot = 0;
    rep(i, 1, n + m) {
        if (k == 1) q[i].x = mxx - q[i].x + 1;
        else if (k == 2) q[i].y = mxy - q[i].y + 1;
        if (q[i].op == 2) umax(x, q[i].x), umax(y, q[i].y);
    }
    rep(i, 1, n + m) if (q[i].x <= x && q[i].y <= y) a[++tot] = q[i];
}
void cdq(int l, int r) {
    if (l == r) return;
    int mid = l + r >> 1;
    cdq(l, mid); cdq(mid + 1, r);
    int x = l, y = mid + 1, z = l;
    for (; y <= r; b[z++] = a[y++]) {
        for (; x <= mid && a[x].x <= a[y].x; b[z++] = a[x++])
            if (a[x].op == 1) add(a[x].y, a[x].x + a[x].y);
        if (a[y].op == 2) umin(ans[a[y].t], a[y].y + a[y].x - ask(a[y].y));
    }
    rep (i, l, x - 1) if (a[i].op == 1) cl(a[i].y);
    rep (i, x, mid) b[z++] = a[i];
    rep (i, l, r) a[i] = b[i];
}
int main() {
    IOS; cin >> n >> m; VI idx;
    rep(i, 1, n) {
        cin >> q[i].x >> q[i].y, q[i].t = i; q[i].op = 1;
        umax(mxx, ++q[i].x); umax(mxy, ++q[i].y);
    }
    rep(i, n + 1, n + m) {
        cin >> q[i].op >> q[i].x >> q[i].y; q[i].t = i;
        umax(mxx, ++q[i].x); umax(mxy, ++q[i].y); ans[i] = 1e9;
        if (q[i].op == 2) idx.pb(q[i].t);
    }
    init(0); cdq(1, tot); init(1); cdq(1, tot);
    init(2); cdq(1, tot); init(1); cdq(1, tot);
    for (auto i : idx) cout << ans[i] << '\n';
    return 0;
}

整体二分

一个环,分成m个区间,每个分别属于一个国家,k个运算,使得[l,r] ([l,m],[1,r])加x,
求每个国家达到自己要求的a[i]最少要经过几次运算(运算顺序不可改),不存在NIE
可用区间树状,也可以用差分(区间修改,单点询问), 板子用的前面的区间改查

struct node { ll op, l, r, id; } q[N << 1], qu[N << 1];
int n, m, _, k;
ll ans[N], c[2][N];
VI a[N];
void solve(int l, int r, int ql, int qr) {
    if (ql > qr) return;
    if (l == r) {
        rep(i, ql, qr) if (q[i].op == 0) ans[q[i].id] = l;
        return;
    }
    int mid = l + r >> 1, nl = ql - 1, nr = 0;
    rep(i, ql, qr)
        if (q[i].op) {
            if (q[i].id > mid) { qu[++nr] = q[i]; continue; }
            if (q[i].l <= q[i].r) add(q[i].l, q[i].r, q[i].op);
            else add(q[i].l, m, q[i].op), add(1, q[i].r, q[i].op);
            //add(m + 1, -q[i].op) m+1并不会执行, 就省略了
            q[++nl] = q[i];
        } else {
            __int128 cur = 0;//3e5*3e5*1e9 爆ll
            for (auto j : a[q[i].id]) cur += ask(j, j);
            if (cur >= q[i].l) q[++nl] = q[i];
            else q[i].l -= cur, qu[++nr] = q[i];
        }
    rep(i, ql, nl) if (q[i].op)
        if (q[i].l <= q[i].r) add(q[i].l, q[i].r, -q[i].op);
        else add(q[i].l, m, -q[i].op), add(1, q[i].r, -q[i].op);
    rep(i, 1, nr) q[i + nl] = qu[i];
    solve(l, mid, ql, nl); solve(mid + 1, r, nl + 1, qr);
}
int main() {
    IOS; cin >> n >> m;
    rep(i, 1, m) cin >> _, a[_].pb(i);
    rep(i, 1, n) cin >> qu[i].l, qu[i].op = 0, qu[i].id = i;
    cin >> k;
    rep(i, 1, k) cin >> q[i].l >> q[i].r >> q[i].op, q[i].id = i;
    ++k; q[k] = { (int)2e9, 1, m, k };
    rep(i, 1, n) q[k + i] = qu[i];
    solve(1, k, 1, k + n);
    rep(i, 1, n) if (ans[i] != k) cout << ans[i] << '\n'; else cout << "NIE\n";
    return 0;
}

可持续化并查集

ll n,m;
struct Lasting_Segment_Tree {
    struct node { ll lson, rson fa, dep; } t[MAXN << 5 | 1];
    ll cnt;
    Lasting_Segment_Tree() { cnt=0; }
    #define rt t[num]
    void build(ll& num, un l = 1, un r = n) {
        num=++cnt;
        if (l==r)rt.fa=l,rt.dep=1;
        else {
            un mid=(l+r)>>1;
            build(rt.lson,l,mid);
            build(rt.rson,mid+1,r);
        }
    }
    void modify(ll& num, ll pre, un pos, ll fa, un l = 1, un r = n) {
    //现在节点是num,前一个版本的现在节点是pre,要将pos位置的fa改为fa
        num = ++cnt; rt=t[pre];
        if(l == r) { rt.fa=fa; return;}
        un mid=(l+r)>>1;
        if(pos<=mid)modify(rt.lson,t[pre].lson,pos,fa,l,mid);
        else modify(rt.rson,t[pre].rson,pos,fa,mid+1,r);
    }
    void add(ll num, un pos, un l = 1, un r = n) {
    //现在节点是num,要讲pos位置深度+1
        if(l==r) { ++rt.dep; return;}
        un mid=(l+r)>>1;
        if(pos<=mid)add(rt.lson,pos,l,mid);
        else add(rt.rson,pos,mid+1,r);
    }
    pll Query(ll num,un pos,un l = 1,un r = n) {
    //返回pos位置的fa和dep
        if (l == r)return pll(rt.fa,rt.dep);
        un mid = (l + r) >> 1;
        if(pos<=mid)return Query(rt.lson,pos,l,mid);
        else return Query(rt.rson,pos,mid+1,r);
    }
}sgt;
ll root[MAXN];//每个版本的根节点
pll find(ll w,ll x) {
//找第w个版本下x的根和深度
    pll tmp=sgt.Query(root[w],x);
    if (tmp.first == x)return tmp;
    return find(w, tmp.first);//不能路径压缩!
}
int main() {
    n = read(), m = read(); ll lastans=0;
    sgt.build(root[0]);
    for(ll i = 1, op = read(); i <= m; ++i) {
        root[i] = root[i-1];//复制
        if(op==1) {
            pll x, y;
            x = find(i, read() ^ lastans); y = find(i, read() ^ lastans);
            if (x.first == y.first) continue;
            if (x.second > y.second) std::swap(x,y);
            sgt.modify(root[i], root[i-1], x.first, y.first);
            if (x.second == y.second) sgt.add(root[i], y.first);
        } else if(op == 2) root[i] = root[read() ^ lastans];
        else {
            ll x = read() ^ lastans, y = read() ^ lastans;
            lastans = (find(i, x).first == find(i, y).first);
            printf("%d\n", lastans);
        }
    }
    return 0;
}

splay

struct Splay {
    static const int N = 1e5 + 5;
    struct Node {
        int fa, val, siz, cnt, l, r, ch[2];
        Node(int Fa = 0, int Val = 0, int Siz = 0, int L = 0, int R = 0):
            fa(Fa), val(Val), siz(Siz), cnt(Siz) { ch[0] = L, ch[1] = R; }
    } t[N];
    int root, tot;
    int getroot() { return t[root].val; }
    bool son(int p) {return p == t[t[p].fa].ch[1]; }
    int newNode(int fa=0,int v=0,int cnt=1){return t[++tot]=Node(fa,v,cnt),tot;}
    void connect(int p, int q, int k) { if (p) t[p].fa = q; t[q].ch[k] = p; }
    void push_up(int p) { t[p].siz = t[t[p].ch[0]].siz + t[t[p].ch[1]].siz + t[p].cnt; }
    void rotate(int p) {
        //if (root == p) return;
        int f = t[p].fa, g = t[f].fa; bool q1 = son(p), q2 = son(f);
        connect(t[p].ch[q1 ^ 1], f, q1); connect(f, p, q1 ^ 1);
        connect(p, g, q2); push_up(f); push_up(p);
    }
    void splay(int p, int q) {
        while (t[p].fa != q) {
            int f = t[p].fa, g = t[f].fa;
            if (g != q) rotate(son(p) ^ son(f) ? p : f);
            rotate(p);
        } if (q == 0) root = p;
    }
    void insert(int x) {
        int p = root, fa = 0;
        for (; p && t[p].val != x; fa = p, p = t[p].ch[x > t[p].val]);
        if (p) ++t[p].cnt;
        else { p = newNode(fa, x); if (fa) t[fa].ch[x > t[fa].val] = p; }
        splay(p, 0);
    }
    void find(int x) {
        if (!root) return; int p = root;
        for (; t[p].val != x && t[p].ch[x > t[p].val]; p = t[p].ch[x > t[p].val]);
        splay(p, 0);
    }
    int next(int x, bool f) { //f=1后继,0前驱
        find(x); int p = root;
        if (f && t[p].val > x) return p;
        if (!f && t[p].val < x) return p;
        for (p = t[p].ch[f]; t[p].ch[f ^ 1]; p = t[p].ch[f ^ 1]);
        return p;
    }
    void detel(int x) {
        int pre = next(x, 0), nex = next(x, 1);
        splay(pre, 0); splay(nex, pre);
        int del = t[nex].ch[0];
        if (t[del].cnt > 1) --t[del].cnt, splay(del, 0);
        else t[nex].ch[0] = 0;
    }
    int kth(int k) {
        if (!root || t[root].siz < k) return -1;
        int p = root;
        while (1) {
            if (t[t[p].ch[0]].siz + t[p].cnt < k)
                k -= t[t[p].ch[0]].siz + t[p].cnt, p = t[p].ch[1];
            else
                if (t[t[p].ch[0]].siz >= k) p = t[p].ch[0]; else return t[p].val;
        }
    }
    int getrk(int x){return find(x), t[root].val != x ? -1 : t[t[root].ch[0]].siz + 1;}
} T;
int main() {
    IOS; cin >> n;
    T.insert(2e9); T.insert(-2e9);
    rep (i, 1, n) {
        int x, op; cin >> op >> x;
        if (op == 1) T.insert(x);
        else if (op == 2) T.detel(x);
        else if (op == 3) cout << T.getrk(x) - 1 << '\n';
        else if (op == 4) cout << T.kth(x + 1) << '\n';
        else if (op == 5) cout << T.t[T.next(x, 0)].val << '\n';
        else cout << T.t[T.next(x, 1)].val << '\n';
    }
    return 0;
}

FHQ 平衡树

纯板子

struct FHQ {
    static const int N = 1e5 + 5;
    struct Node {
        int ch[2], val, pri, siz;
        Node (int S = 0, int V = 0, int P = 0, int l = 0, int r = 0) :
            siz(S), val(V), pri(P) { ch[0] = l, ch[1] = r; }
        int& operator [](const int k) { return ch[k]; }
    } tr[N];
    FHQ() { srand((unsigned)time(NULL)); }
    int tot, x, y, z, root;
    int newNode (int v) { tr[++tot] = Node(1, v, rand()); return tot; }
    void update(int x) { tr[x].siz = 1 + tr[tr[x][0]].siz + tr[tr[x][1]].siz; }
    int merge(int x, int y) {
        if (!x || !y) return x + y;
        if (tr[x].pri<tr[y].pri) {tr[x][1] = merge(tr[x][1], y); update(x); return x;}
        else { tr[y][0] = merge(x, tr[y][0]); update(y); return y; }
    }
    void split_v(int p, int k, int &x, int& y) {
        if (!p) x = y = 0;
        else {
            if (tr[p].val <= k) x = p, split_v(tr[p][1], k, tr[p][1], y);
            else y = p, split_v(tr[p][0], k, x, tr[p][0]);
            update(p);
        }
    }
    void split_k(int p, int k, int &x, int &y) {
        if (!p) x = y = 0;
        else {
            if (k <= tr[tr[p][0]].siz) y = p, split_k(tr[p][0], k, x, tr[p][0]);
            else x = p, split_k(tr[p][1], k - tr[tr[p][0]].siz - 1, tr[p][1], y);
            update(p);
        }
    }
    int kth(int p, int k) {
        while (1) {
            if (k <= tr[tr[p][0]].siz) p = tr[p][0];
            else if (k == tr[tr[p][0]].siz + 1) return p;
            else k -= tr[tr[p][0]].siz + 1, p = tr[p][1];
        }
    }
    int getrk(int val) {
        split_v(root, val - 1, x, y);
        int ans = tr[x].siz + 1;
        return root = merge(x, y), ans;
    }
    void add_v(int pos, int val) {
        split_v(root, pos, x, y);
        root = merge(merge(x, newNode(val)), y);
    }
    void del_v(int val) {
        split_v(root, val, x, z);
        split_v(x, val - 1, x, y);
        y = merge(tr[y][0], tr[y][1]);
        root = merge(merge(x, y), z);
    }
    void del_k(int k) {
        split_k(root, k, x, z);
        split_k(x, k - 1, x, y);
        y = merge(tr[y][0], tr[y][1]);
        root = merge(merge(x, y), z);
    }
    int pre(int val) {
        split_v(root, val - 1, x, y);
        int ans = tr[kth(x, tr[x].siz)].val;
        return root = merge(x, y), ans;
    }
    int nxt(int val) {
        split_v(root, val, x, y);
        int ans = tr[kth(y, 1)].val;
        return root = merge(x, y), ans;
    }
} T;

区间反转/轮换

struct FHQ {
    static const int N = 5e5 + 5;
    struct Node {
        int ch[2], pri, siz; ll tag, val, miv; bool rever;
        int& operator [](const int k) { return ch[k]; }
    } tr[N];
    FHQ () { srand((unsigned)time(NULL)); }
    int tot, x, y, z, root;
    int newNode (int val) {
        tr[++tot].val = val; tr[tot].pri = rand(); tr[tot].tag = 0;
        tr[tot].rever = 0; tr[tot][0] = tr[tot][1] = 0;
        return tr[tot].miv = val, tr[tot].siz = 1, tot;
    }
    void push_up(int p) {
        tr[p].siz = 1 + tr[tr[p][0]].siz + tr[tr[p][1]].siz; tr[p].miv = tr[p].val;
        if (tr[p][0]) umin(tr[p].miv, tr[tr[p][0]].miv);
        if (tr[p][1]) umin(tr[p].miv, tr[tr[p][1]].miv);
    }
    void push_down(int p) {
        tr[tr[p][0]].tag += tr[p].tag; tr[tr[p][1]].tag += tr[p].tag;
        tr[tr[p][0]].val += tr[p].tag; tr[tr[p][1]].val += tr[p].tag;
        tr[tr[p][0]].miv += tr[p].tag; tr[tr[p][1]].miv += tr[p].tag;
        if (tr[p].rever) 
            tr[tr[p][0]].rever^=1, tr[tr[p][1]].rever^=1, swap(tr[p][0],tr[p][1]);
        tr[p].rever = tr[p].tag = 0;
    }
    int merge(int x, int y) {
        if (!x || !y) return x + y; push_down(x); push_down(y);
        if (tr[x].pri < tr[y].pri) tr[x][1] = merge(tr[x][1], y);
        else tr[y][0] = merge(x, tr[y][0]), swap(x, y);
        push_up(x); return x;
    }
    void split(int p, int k, int &x, int &y) {
        if (!p) x = y = 0;
        else {
            push_down(p);
            if (k <= tr[tr[p][0]].siz) y = p, split(tr[p][0], k, x, tr[p][0]);
            else x = p, split(tr[p][1], k - tr[tr[p][0]].siz - 1, tr[p][1], y);
            push_up(p);
        }
    }
    void add(int pos, int val) {
        split(root, pos, x, y); root = merge(merge(x, newNode(val)), y);
    }
    void del(int k){split(root,k,x,z);split(x,k-1,x,y);root=merge(x, z);}
    void res(int l, int r) { //反转l, r
        split(root, r, x, z); split(x, l - 1, x, y);
        tr[y].rever ^= 1; root = merge(merge(x, y), z);
    }
    void rev(int l, int r, ll t) { //[l,r] t次轮换
        t %= (r - l + 1); if (!t) return;
        split(root,r,root,z);split(root,r-t,root,y);split(root,l-1,root,x);
        root = merge(merge(root, y), merge(x, z));
    }
    void change(int l, int r, ll k) { //[l,r] 区间+k
        split(root, r, x, z); split(x, l - 1, x, y);
        tr[y].tag += k, tr[y].val += k, tr[y].miv += k; root = merge(merge(x, y), z);
    }
    ll minval(int l, int r) { //[l,r]最小值
        split(root, r, x, z); split(x, l - 1, x, y);
        ll ans = tr[y].miv; return root = merge(merge(x, y), z), ans;
    }
} T;

FHQ启发式合并

struct FHQ {
    static const int N = 4e5 + 5;
    FHQ() { srand(time(0)); }
    struct Node {
        int ch[2], val, pri; ll siz, cnt;
        int& operator [](int k) { return ch[k]; }
    } tr[N];
    int rt[N], tot;
    void push_up(int p){tr[p].siz = tr[tr[p][0]].siz + tr[tr[p][1]].siz + tr[p].cnt;}
    int newNode(int v, ll c) {
        tr[++tot].val = v; tr[tot].siz = tr[tot].cnt = c;
        return tr[tot][0] = tr[tot][1] = 0; tr[tot].pri = rand(), tot;
    }
    void split(int p, int k, int& x, int& y) {
        if (!p) x = y = 0;
        else {
            if (tr[p].val <= k) x = p, split(tr[p][1], k, tr[p][1], y);
            else y = p, split(tr[p][0], k, x, tr[p][0]);
            push_up(p);
        }
    }
    int merge(int x, int y) {
        if (!x || !y) return x | y;
        if (tr[x].pri < tr[y].pri) tr[x][1] = merge(tr[x][1], y);
        else tr[y][0] = merge(x, tr[y][0]), swap(x, y);
        push_up(x); return x;
    }
    int split(int& p, int l, int r) {
        int x,y,z;split(p,r,x,z);split(x,l-1,x,y);p=merge(x,z);return y;
    }
    int unit(int x, int y) {
        if (!x || !y) return x | y;
        if (tr[x].pri > tr[y].pri) swap(x, y);
        int a, b, c; split(y, tr[x].val, a, b); split(a, tr[x].val - 1, a, c);
        if (c) tr[x].cnt += tr[c].cnt, tr[x].siz += tr[c].siz;
        tr[x][0] = unit(tr[x][0], a); tr[x][1] = unit(tr[x][1], b);
        push_up(x); return x;
    }
    void insert(int& p, int v, ll c) {
        if (!c) return;
        int x, y, z; split(p, v, x, z); split(x, v - 1, x, y);
        if (!y) y = newNode(v, c);
        else tr[y].cnt += c, tr[y].siz += c;
        p = merge(merge(x, y), z);
    }
    ll ask(int& p, int l, int r) {
        int x, y, z; split(p, r, x, z); split(x, l - 1, x, y);
        ll ans = tr[y].siz; p = merge(merge(x, y), z);return ans;
    }
    int kth(int p, ll k) {
        if (k > tr[p].siz) return -1;
        while (1)
            if (tr[tr[p][0]].siz >= k) p = tr[p][0];
            else if (tr[tr[p][0]].siz + tr[p].cnt >= k) return tr[p].val;
            else k -= tr[tr[p][0]].siz + tr[p].cnt, p = tr[p][1];
    }
} T;
int n, m, _, k, tot = 1;
void solve0() { //把p中值域[x,y]分出建新树
    int p, x, y; cin >> p >> x >> y;
    T.rt[++tot] = T.split(T.rt[p], x, y);
}
void solve1() { //合并y和x T.rt[x] = T.rt[y]
    int x, y; cin >> x >> y;
    T.rt[x] = T.unit(T.rt[x], T.rt[y]);
}
void solve2() { //在树p加入y个x
    int p, x, y; cin >> p >> x >> y;
    T.insert(T.rt[p], y, x);
}
ll solve3() { //求树p值域[x,y]数的个数
    int p, x, y; cin >> p >> x >> y;
    return T.ask(T.rt[p], x, y);
}
int solve4() { //求树p中第k小
    ll p, k; cin >> p >> k;
    return T.kth(T.rt[p], k);
}

数学

素数

米勒拉宾判素数

bool millerRabbin(int n) {
    if (n < 3) return n == 2;
    int a = n - 1, b = 0;
    while (a % 2 == 0) a /= 2, ++b;
    srand(time(0));
    //test_time为测试次数,建议设为不小于8,也不应太大
    for (int i = 1, j; i <= test_time; ++i) {
        int x = rand() % (n - 2) + 2, v = qpow(x, a, n);//x^a%n
        if (v == 1 || v == n - 1) continue;
        for (j = 0; j < b; ++j) {
            v = (long long)v * v % n;
            if (v == n - 1) break;
        }
        if (j >= b) return 0;
    }
    return 1;
}

反素数

如果某个正整数n满足如下条件,则称为是反素数:任何小于n的正数的约数个数都小于n的约数个数,即因子最多的数(因子数相同取最小的数)

因子数为n的最小数

ull p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}, ans;
int n;
// depth: 当前在枚举第几个素数。num: 当前因子数。
// temp: 当前因子数量为num的时候的数值。
// up:上一个素数的幂,这次应该小于等于这个幂次
void dfs(int depth, ull temp, int num, int up) {
    if (num > n || depth > 15) return;
    if (num == n && ans > temp) { ans = temp; return; }
    for (int i = 1; i <= up; ++i) {
        if (temp * p[depth] >= ans) break;
        dfs(depth + 1, temp *= p[depth], num * (i + 1), i);
    }
}
int main() {
    while (scanf("%d", &n) != EOF) {
        ans = ~0ULL; dfs(0, 1, 1, 64);
        printf("%llu\n", ans);
    }
    return 0;
}

小于n因子数最大的数

ull p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}, ans;
int ans_num, n; //ans为n以内的最大反素数(会持续更新),ans_sum为ans的因子数。
void dfs(int depth, ull temp, int num, int up) {
    if (depth > 15 || temp > n) return;
    if (num > ans_num) { ans = temp; ans_num = num; }
    if (num == ans_num && ans > temp) ans = temp;
    for (int i = 1; i <= up; ++i) {
        if (temp * p[depth] > n) break;
        dfs(depth + 1, temp *= p[depth], num * (i + 1), i);
    }
}
int main() {
    while (scanf("%d", &n) != EOF) {
        ans_num = 0; dfs(0, 1, 1, 64);
        printf("%llu\n", ans);
    }
    return 0;
}

公约数/倍数

gcd

gcd(a, b) = gcd(b, a-b) = gcd(a, a-b) = gcd(b, a % b) = gcd(a+b, a)
gcd(a, b, c, d, .., z) = gcd(a, b - a, c, d, ..., z - a)
gcd(a[i], a[i + 1], ..., a[j]) = gcd(a[i], a[i + 1] - a[i], ..., a[j] - a[j - 1])
这样对于数组区间修改相当于在差分数组上单点修改, 可用线段树维护区间gcd, a[i]用树状维护

lcm

lcm[a, b] = c * b / gcd(a, b)

扩展欧几里得

ll exgcd(ll a, ll b, ll& x, ll& y) {
    if (b == 0) { x = 1, y = 0; return a; }
    ll d = exgcd(b, a % b, y, x); y -= a / b * x;
    return d;
}

欧拉函数

求n的欧拉函数

int getphi(int n) {
    int ans = n;
    rep (i, 2, n / i) if (n % i == 0) {
        ans = ans / i * (i - 1);
        while (n % i == 0) n /= i;
    }
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}

筛法

Euler筛法(pri, phi, mu)

int v[N], pri[N], phi[N], mu[N], tot;
void init(int n) {
    memset(v, 0, sizeof v); tot = 0; mu[1] = 1;
    rep (i, 2, n) {
        if (v[i] == 0) mu[v[i] = pri[++tot] = i] = -1, phi[i] = i - 1;
        rep (j, 1, tot) {
            if (pri[j] > v[i] || pri[j] > n / i) break;
            v[i * pri[j]] = pri[j]; //每个数的最小质因子
            phi[i * pri[j]] = phi[i] * (pri[i] - (i % pri[j] != 0));
            mu[i * pri[j]] = i % pri[j] ? -mu[i] : 0;
        }
    }
}

Euler筛法(约数个数)

约数个数定理: 若\(n=\Pi^m_{i=1}p_i^{c_i}\), 则\(cnt=\Pi^m_{i=1}c_i+1\)
\(d_i=\Pi^m_{i=1}c_i+1\) 是积性函数, 自然可以用Euler筛法

int v[N], pri[N], d[N], num[N], tot; //num最小质数个数
void init(int n) {
    memset(v, 0, sizeof v); tot = 0; d[1] = 1;
    rep (i, 2, n) {
        if (v[i] == 0) v[i] = pri[++tot] = i, d[i] = 2, num[i] = 1;
        rep (j, 1, tot) {
            if (pri[j] > v[i] || pri[j] > n / i) break;
            int cur = i * pri[j]; v[cur] = pri[j]; //每个数的最小质因子
            num[cur] = i % pri[j] ? 1 : num[i] + 1;
            d[cur] = i % pri[j] ? d[i] * 2 : d[i] / num[cur] * (num[cur] + 1);
        }
    }
}

Euler筛法(约数和)

int v[N], pri[N], g[N], f[N], tot;//f[i]i约数和, g[i]i的最小质因子的等比数列和,必要开ll
void init(int n) {
    memset(v, 0, sizeof v); tot = 0; f[1] = g[1] = 1;
    rep (i, 2, n) {
        if (v[i] == 0) v[i] = pri[++tot] = i, f[i] = g[i] = i + 1;
        rep (j, 1, tot) {
            if (pri[j] > v[i] || pri[j] > n / i) break;
            int cur = i * pri[j]; v[cur] = pri[j]; //每个数的最小质因子
            g[cur] = i % pri[j] ? 1 + pri[j] : g[i] * pri[j] + 1;
            f[cur] = i % pri[j] ? f[i] * f[pri[j]] : f[i] / g[i] * g[cur];
        }
    }
    rep (i, 1, n) f[i] = (f[i - 1] + f[i]) % mod;
}

欧拉定理 && 费马小定理 && 裴蜀定理

费马小定理

若p为素数, \(gcd(p, a)=1\), 则\(a^{p-1}\equiv 1(mod \ p)\)
对任意整数 \(a^p\equiv a(mod \ p)\)

欧拉定理

\(gcd(a,m)=1\), 则\(a^{\varphi (m)}\equiv 1\ (mod \ m)\)

扩展欧拉定理

\(a^b(mod \ p) \equiv \left\{\begin{matrix} a^{b \ mod \ \varphi (p)}, & gcd(a,p)=1\\ a^b, & gcd(a,p)\neq 1,b\leqslant \varphi (p)\\ a^{b \ mod \ \varphi (p) + \varphi (p)}, & gcd(a,p) \neq 1,b \geqslant \varphi(p) \end{matrix}\right.\)

裴蜀定理

任意x,y不全为零, 存在\(ax+by=gcd(a,b)\)

乘法逆元

单个逆元

快速幂\(a^{-1}(mod \ p) \equiv a^{p-2}(mod \ p)\)

线性逆元

inv[0] = inv[1] = 1;
rep (i, 2, n) inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;

线性求任意 n 个数的逆元

s[0] = 1;
rep (i, 1, n) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
per (i, n, 1) sv[i - 1] = sv[i] * a[i] % p;
rep (i, 1, n) inv[i] = sv[i] * s[i - 1] % p;

线性同余方程

\(ax\equiv c(mod \ b)\)等价于\(ax+by=c\)
\(gcd(a,b)|c\)时有解, \(ax_0+by_0=gcd(x,y)\)
通解为\(x=\frac{c}{gcd(a,b)}x_0+k\frac{b}{gcd(a,b)}, \ y=\frac{c}{gcd(a,b)}y_0-k\frac{a}{gcd(a,b)}\)

中国剩余定理

模数两两互质

ll CRT(ll a[], ll m[], int n) {
    ll t = 1, ans = 0;
    for (int i = 0; i < n; ++i) t *= m[i];
    for (int i = 0; i < n; ++i) {
        ll cur = t / m[i], x, y; exgcd(cur, m[i], x, y);
        ans = (ans + a[i] * cur % t * x % t) % t;
    }
    return (ans + t) % t;
}

模数不互质

\(\left\{\begin{matrix} x \equiv a_1 (mod \ m_1)\\ x \equiv a_2 (mod \ m_2)\\ ...\\ x \equiv a_k (mod \ m_k)\end{matrix}\right.\)
\(x=m_1p+a_1=m_2q+a_2 \ \ p,q∈Z\), 则\(m_1p-m_2q=a_2-a_1\)
由裴蜀定理得\(a_2-a_1=0(mod \ gcd(m_1,m_2))\)否则无解
则合并两项得出通解\(x \equiv m_1p+a_1(mod \ lcm(m_1,m_2))\), 不断合并求出x

ll mod(ll x, ll p) { return (x % p + p) % p; }
ll CRT(ll a[], ll m[], int n) {
    for (int i = 2; i <= n; ++i) {
        ll k1, k2, d = exgcd(m[1], m[i], k1, k2);
        ll c = mod(a[i] - a[1], m[i]);
        if (c % d) return -1;
        ll p = m[i] / d; c = c / d % p; k1 = mod(k1 * c, p);
        a[1] += m[1] * k1; m[1] = m[1] / d * m[i];
    }
    return a[1];
}

baby-step giant-step

\(a^x\equiv b(mod \ p), a\perp p\)

ll baby_step_giant_step(ll a, ll b, ll p) {
    unordered_map<ll, ll> st; b %= p;
    int t = sqrt(p - 1) + 1; ll cur = 1;
    for (int i = 0; i < t; ++i, cur = cur * a % p) st[b * cur % p] = i;
    a = qpow(a, t, p); cur = a;
    if (a == 0) return b == 0 ? 1 : -1;
    for (int i = 1; i <= t; ++i, cur = cur * a % p) {
        ll c = st.count(cur) ? st[cur] : -1;
        if (c >= 0 && i * t - c >= 0) return i * t - c;
    }    
    return -1;
}

\(a^x\equiv b(mod \ p)\)

具体地,设\(d_1=gcd(a,p)\)。如果\(d_1\nmid b\),则原方程无解。否则我们把方程同时除以\(b_1\),得到

\(\frac{a}{d_1}a^{x-1}\equiv \frac{b}{d_1}(mod \ \frac{p}{d_1})\)

如果\(a\)\(p\)仍不互质就再除,设\(d_2=gcd(a,p)\)。如果\(d_2\nmid \frac{b}{d_1}\),则方程无解;否则同时除以\(d_2\)得到

\(\frac{a^2}{d_1d_2}a^{x-2}\equiv \frac{b}{d_1d_2}(mod \ \frac{p}{d_1d_2})\)

同理,这样不停的判断下去。直到\(a\perp \frac{p}{d_1d_2...d_k}\)

\(D=\prod_{i=1}^k d_i\),于是方程就变成了这样:\(\frac{a^k}{D}a^{x-k}\equiv \frac{b}{D}(mod \ \frac{p}{D})\)

由于\(a\perp\frac{p}{D}\),于是\(\frac{a^k}{D}\perp\frac{p}{D}\)推出。这样\(\frac{a^k}{D}\)就有逆元了,于是把它丢到方程右边,这就是一个普通的\(BSGS\)问题了,于是求解\(x-k\)后再加上\(k\)就是原方程的解啦。

注意,不排除解小于等于\(k\)的情况,所以在消因子之前做一下\(O(k)\)枚举,直接验证\(a^i\equiv b(mod \ p)\)

高斯消元

无模数(用double)

double a[N][N];
int gauss(int n, int m) {
    int c = 1, r = 1;
    for (int t = r; c < m && r <= n; ++c, t = r) {
        rep (i, r + 1, n) if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
        if (fabs(a[t][c]) < eps) continue;
        if (t != r) rep (i, 1, m) swap(a[t][i], a[r][i]);
        rep (i, c + 1, m) a[r][i] /= a[r][c]; a[r][c] = 1;
        for (int i = r + 1; i <= n; a[i++][c] = 0) if (a[i][c])
            rep (j, c + 1, m) a[i][j] -= a[i][c] * a[r][j];
        ++r;
    }
    rep (i, r, n) if (a[i][m]) return -1;
    if (r < m) return 0;
    per (i, m - 2, 1) rep (j, i + 1, m - 1) a[i][m] -= a[j][m] * a[i][j];
    return 1;
}

有模数(用费马定理)

int a[N][N];
int gauss(int n, int m) {
    int c = 1, r = 1;
    for (int t = -1; c < m && r <= n; ++c, t = -1) {
        rep (i, r, n) if (a[i][c]) { t = i; break; }
        if (t == -1) continue;
        if (t != r) swap(a[t], a[r]);
        a[r][c] = qpow(a[r][c], mod - 2);
        rep (i, c + 1, m) a[r][i] = (ll)a[r][i] * a[r][c] % mod; a[r][c] = 1;
        for (int i = r + 1; i <= n; a[i++][c] = 0) if (a[i][c])
            rep (j, c + 1, m) a[i][j]=((a[i][j] - a[r][j] * a[i][c])%mod+mod)%mod;
        ++r;
    }
    rep (i, r, n) if (a[i][m]) return -1;
    if (r < m) return 0;
    per (i, m - 1, 1) rep (j, i + 1, m - 1)
        a[i][m] = ((a[i][m] - a[j][m] * a[i][j]) % mod + mod) % mod;
    return 1;
}

异或消元

bitset<N> d[N];
ll gauss(int n, int m) {
    int c = 1, r = 1;
    for (int t = r; c <= m && r <= n; ++c, t = ++r) {
        rep(i, r + 1, n) if (d[i][c]) t = i;
        if (!d[t][c]) { --r; continue; }
        if (t != r) swap(d[t], d[r]);
        for (int i = r + 1; i <= n; d[i++][c] = 0) if (d[i][c])
            d[i] ^= d[r];
    }
    return n - r + 1;
}

线性基

struct XXJ {
    static const int N = 59;
    ll g[N + 1]; bool zero = 0; vector<ll> a;
    void init() { memset(g, 0, sizeof g); zero = 0; }
    bool insert(ll x) {
        per (i, N, 0) if (x >> i & 1)
            if (!g[i]) {g[i] = x; return 1; } else x ^= g[i];
        return zero = 1, 0;
    }
    bool isin(ll x) { per (i, N, 0) if (x >> i & 1) x ^= g[i]; return !x; }
    ll max() { ll mx = 0; per (i, N, 0) umax(mx, mx ^ g[i]); return mx; }
    ll min() { if (zero) return 0; rep (i, 0, N) if (g[i]) return g[i]; }
    void build() {
        vector<ll>().swap(a);
        rep (i, 0, N) {
            per (j, i - 1, 0) if (g[i] >> j & 1) g[i] ^= g[j];
            if (g[i]) a.emplace_back(g[i]);
        }
    }
    ll kth(ll k) {
        ll ans = 0; k -= zero; if (k <= 0) return !k ? 0 : -1;
        if (k >> a.size()) return -1;
        rep (i, 0, a.size() - 1) if (k >> i & 1) ans ^= a[i];
        return ans;
    }
} xxj;

排列组合

多重集的组合数1

\(S=\{n_1*a_1,n_2*a_2,...,n_k*a_k\}\)选r个元素\((r \leqslant min(n_1))\), 组合数为\(S=C_{r+(k-1)}^{r-1}\)

多重集的组合数2

\(S=\{n_1*a_1,n_2*a_2,...,n_k*a_k\}\)选r个元素, 组合数为\(|\bigcap_i^k \sum S_i|=S-|\bigcup_i^k \sum \bar{S_i}|=\sum_iC_{r+k-1-(n_i+1)}^{k-1} - \sum_{i,j}C_{r+k-1-(n_i+1)-(n_j+1)}^{k-1}+..+(-1)^kC_{r-1-\sum_{i=k}^kn_i}^{k-1}\)

不相邻的排列

从1~n个数选k个数互不相邻, \(S=C_{n-k+1}^k\)

错排公式

\(f(n)=(n-1)(f(n-1)+f(n-2))=\left \lfloor n!/e+0.5 \right \rfloor\)

园排列

\(Q_n^r=\frac{A_n^r}{r}=\frac{n!}{r*(n-r)!}\)

Catlan

n个0, n个1, 任意前缀0个数大于1的个数, \(Cat_n = \frac{C^n_{2n}}{n+1}\)
n对括号匹配, n个数进出栈, n个节点构成二叉树, (0,0)->(n,m)除端点不接触y=x走法\(2Cat_{n-1}\)

Lucas定理

\(C_n^m=C_{n \ mod \ p}^{m \ mod \ p} * Lucas_{n/p}^{m/p} \ \ mod \ p\)

ll C(int n, int m, int k) {
    return m>n?0:fac[k][n]*facinv[k][m]%mod[k]*facinv[k][n - m]%mod[k];
}
ll lucas(ll n, ll m, int k) {
    return m?C(n%mod[k],m%mod[k],k)*lucas(n/mod[k],m/mod[k],k)%mod[k]:1;
}

莫比乌斯

miu[1] = 1;
rep (i, 2, n) {
    if (!v[i]) prime[++tot] = i, miu[i] = -1;
    for (int j = 1; prime[j] <= n / i && j <= tot; ++j) {
        v[prime[j] * i] = 1;
        if(i % prime[j] == 0) break;
        else miu[i * prime[j]] = -miu[i];
    }
}

进制任意转换

void work(int a, int b, string& s) {
    int len = int(s.size());
    VI cur(len), ans;
    rep(i, 0, len - 1) cur[i] = s[i] - (s[i]<='9'?'0':s[i]<='Z'?'A'-9:'a'-35);
    for (int i = 0; i < len;) {
        rep (j, i + 1, len - 1) cur[j] += cur[j - 1] % b * a, cur[j - 1] /= b;
        ans.pb(cur[len - 1] % m); cur[len - 1] /= m;
        while (i < len && cur[i] == 0) ++i;
    } s = "";
    per(i,ans.size()-1,0)s+=char(ans[i]+(ans[i]<=9?'0':ans[i]<=35?'A'-9:'a'-35);
}

模拟退火

const double eps = 1e-3;       //精度
const double start_T = 1000;   //初始温度
const double rate = 0.98;      //温度下降速率
int n, m, _, k;
struct point {
    double x;
    double y;
    double z;
} p[N];
double dist(point a, point b) {
    return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z));
}
double solve() {
    double T = start_T;
    point ans_p = {0,0,0};  //初始点
    double ans = 1e99;      //预设一个较大值
    while (T > eps) {
        point maxd_p = p[1];
        rep (i, 1, n)
            if (dist(ans_p, p[i]) > dist(ans_p, maxd_p)) maxd_p = p[i];
        //找到距离ans_p最远的点,maxd_p
        ans = min(ans, dist(ans_p, maxd_p));
        ans_p.x += (maxd_p.x - ans_p.x) * (T / start_T);    //以一定概率靠近maxd_p
        ans_p.y += (maxd_p.y - ans_p.y) * (T / start_T);
        ans_p.z += (maxd_p.z - ans_p.z) * (T / start_T);
        T *= rate;
    }
    return ans;
}
int main() {
    IOS; cin >> n;
    rep(i, 1, n) cin >> p[i].x >> p[i].y >> p[i].z;
    cout << setiosflags(ios::fixed) << setprecision(8) << solve();
    return 0;
}

__builtin函数 (__int128适用)

__builtin_popcount(x); // x二进制1的个数
__builtin_ffs(x); //比lowbit慢
__bultin_ctz(x); //x二进制末尾0的个数
__bultin_clz(x); //二进制前导0, 0未知
__builtin_parity(x);//x的二进制数中1的个数的奇偶性

大数

#define MAXN 9999 // MAXN 是一位中最大的数字
#define MAXSIZE 10024 // MAXSIZE 是位数
#define DLEN 4 // DLEN 记录压几位
struct Big {
    int a[MAXSIZE], len;
    bool flag;  // 标记符号'-'
    Big() { len = 1; memset(a, 0, sizeof a); flag = 0; }
    Big(const int);
    Big(const char*);
    Big(const Big&);
    Big& operator=(const Big&);
    Big operator+(const Big&) const;
    Big operator-(const Big&) const;
    Big operator*(const Big&)const;
    Big operator/(const int&) const;
    Big operator^(const int&) const;
    // TODO: Big 位运算;
    int operator%(const int&) const;
    // TODO: Big ^ Big;
    bool operator<(const Big&) const;
    bool operator<(const int& t) const;
    inline void print() const;
};
Big::Big(const int b) {
    int c, d = b; len = 0;
    CLR(a); //memset(a,0,sizeof a);
    while (d > MAXN) {
        c = d - (d / (MAXN + 1) * (MAXN + 1));
        d = d / (MAXN + 1); a[len++] = c;
    }
    a[len++] = d;
}
Big::Big(const char* s) {
    int t, k, index, l; CLR(a);
    l = strlen(s); len = l / DLEN;
    if (l % DLEN) ++len;
    index = 0;
    for (int i = l - 1; i >= 0; i -= DLEN) {
        t = 0; k = i - DLEN + 1;
        if (k < 0) k = 0;
        g(j, k, i) t = t * 10 + s[j] - '0';
        a[index++] = t;
    }
}
Big::Big(const Big& T) : len(T.len) {
    CLR(a);
    for (int i = 0; i < len; ++i) a[i] = T.a[i];
    // TODO:重载此处?
}
Big& Big::operator=(const Big& T) {
    CLR(a); len = T.len;
    for (int i = 0; i < len; ++i) a[i] = T.a[i];
    return *this;
}
Big Big::operator+(const Big& T) const {
    Big t(*this);
    int big = len;
    if (T.len > len) big = T.len;
    for (int i = 0; i < big; ++i) {
        t.a[i] += T.a[i];
        if (t.a[i] > MAXN) { ++t.a[i + 1]; t.a[i] -= MAXN + 1; }
    }
    if (t.a[big]) t.len = big + 1;
    else t.len = big;
    return t;
}
Big Big::operator-(const Big& T) const {
    int big;
    bool ctf;
    Big t1, t2;
    if (*this < T) {
        t1 = T; t2 = *this; ctf = 1;
    }
    else { t1 = *this; t2 = T; ctf = 0; }
    big = t1.len;
    int j = 0;
    for (int i = 0; i < big; ++i) {
        if (t1.a[i] < t2.a[i]) {
            j = i + 1;
            while (t1.a[j] == 0) ++j;
            --t1.a[j--];
            // WTF?
            while (j > i) t1.a[j--] += MAXN;
            t1.a[i] += MAXN + 1 - t2.a[i];
        }
        else t1.a[i] -= t2.a[i];
    }
    t1.len = big;
    while (t1.len > 1 && t1.a[t1.len - 1] == 0) { --t1.len; --big; }
    if (ctf) t1.a[big - 1] = -t1.a[big - 1];
    return t1;
}
Big Big::operator*(const Big& T) const {
    Big res;
    int up, te, tee;
    for (int i = 0; i < len; ++i) {
        up = 0;
        for (int j = 0; j < T.len; ++j) {
            te = a[i] * T.a[j] + res.a[i + j] + up;
            if (te > MAXN) {
                tee = te - te / (MAXN + 1) * (MAXN + 1);
                up = te / (MAXN + 1);
                res.a[i + j] = tee;
            }
            else { up = 0; res.a[i + j] = te; }
        }
        if (up) res.a[i + T.len] = up;
    }
    res.len = len + T.len;
    while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
    return res;
}
Big Big::operator/(const int& b) const {
    Big res;
    int down = 0;
    gd(i, len - 1, 0) {
        res.a[i] = (a[i] + down * (MAXN + 1) / b);
        down = a[i] + down * (MAXN + 1) - res.a[i] * b;
    }
    res.len = len;
    while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
    return res;
}
int Big::operator%(const int& b) const {
    int d = 0;
    gd(i, len - 1, 0) d = (d * (MAXN + 1) % b + a[i]) % b;
    return d;
}
Big Big::operator^(const int& n) const {
    Big t(n), res(1);
    int y = n;
    while (y) {
        if (y & 1) res = res * t;
        t = t * t;
        y >>= 1;
    }
    return res;
}
bool Big::operator<(const Big& T) const {
    int ln;
    if (len < T.len) return 233;
    if (len == T.len) {
        ln = len - 1;
        while (ln >= 0 && a[ln] == T.a[ln]) --ln;
        if (ln >= 0 && a[ln] < T.a[ln]) return 233;
        return 0;
    }
    return 0;
}
inline bool Big::operator<(const int& t) const {
    Big tee(t);
    return *this < tee;
}
inline void Big::print() const {
    printf("%d", a[len - 1]);
    gd(i, len - 2, 0) { printf("%04d", a[i]); }
}
inline void print(Big s) { // s不要是引用,要不然你怎么print(a * b);
    int len = s.len;
    printf("%d", s.a[len - 1]);
    gd(i, len - 2, 0) { printf("%04d", s.a[i]); }
}
char s[100024];

计算几何二维

const int N = 262144 + 3;

/*一:【准备工作】*/
#define int register int
const double eps = 1e-8, Pi = acos(-1.0);
inline int dcmp(double a) { return a < -eps ? -1 : (a > eps ? 1 : 0); }//处理精度
inline double Abs(double a) { return a * dcmp(a); }//取绝对值
struct Point {
    double x, y; Point(double X = 0, double Y = 0) { x = X, y = Y; }
    inline void in() { scanf("%lf%lf", &x, &y); }
    inline void out() { printf("%.2lf %.2lf\n", x, y); }
};

/*二:【向量】*/
inline double Dot(Point a, Point b) { return a.x * b.x + a.y * b.y; }//【点积】
inline double Cro(Point a, Point b) { return a.x * b.y - a.y * b.x; }//【叉积】
inline double Len(Point a) { return sqrt(Dot(a, a)); }//【模长】
inline double Angle(Point a, Point b) { return acos(Dot(a, b) / Len(a) / Len(b)); }//【两向量夹角】
inline Point Normal(Point a) { return Point(-a.y, a.x); }//【法向量】
inline Point operator+(Point a, Point b) { return Point(a.x + b.x, a.y + b.y); }
inline Point operator-(Point a, Point b) { return Point(a.x - b.x, a.y - b.y); }
inline Point operator*(Point a, double b) { return Point(a.x * b, a.y * b); }
inline bool operator==(Point a, Point b) { return !dcmp(a.x - b.x) && !dcmp(a.y - b.y); }
//两点坐标重合则相等

/*三:【点、向量的位置变换】*/
/*1.【点、向量的旋转】*/
inline Point turn_P(Point a, double theta) {
    //【点A\向量A顺时针旋转theta(弧度)】
    double x = a.x * cos(theta) + a.y * sin(theta);
    double y = -a.x * sin(theta) + a.y * cos(theta);
    return Point(x, y);
}
inline Point turn_PP(Point a, Point b, double theta) {
    //【将点A绕点B顺时针旋转theta(弧度)】
    double x = (a.x - b.x) * cos(theta) + (a.y - b.y) * sin(theta) + b.x;
    double y = -(a.x - b.x) * sin(theta) + (a.y - b.y) * cos(theta) + b.y;
    return Point(x, y);
}


/*四:【图形与图形之间的关系】*/

/*1.【点与线段】*/
inline int pan_PL(Point p, Point a, Point b) {//【判断点P是否在线段AB上】
    return !dcmp(Cro(p - a, b - a)) && dcmp(Dot(p - a, p - b)) <= 0;//做法一
//  return !dcmp(Cro(p-a,b-a))&&dcmp(min(a.x,b.x)-p.x)<=0&&
//  dcmp(p.x-max(a.x,b.x))<=0&&dcmp(min(a.y,b.y)-p.y)<=0&&
//  dcmp(p.y-max(a.y,b.y))<=0;//做法二
    //PA,AB共线且P在AB之间(其实也可以用len(p-a)+len(p-b)==len(a-b)判断,但是精度损失较大)
}
inline double dis_PL(Point p, Point a, Point b) {//【点P到线段AB距离】
    if (a == b)return Len(p - a);//AB重合
    Point x = p - a, y = p - b, z = b - a;
    if (dcmp(Dot(x, z)) < 0)return Len(x);//P距离A更近
    if (dcmp(Dot(y, z)) > 0)return Len(y);//P距离B更近
    return Abs(Cro(x, z) / Len(z));//面积除以底边长
}

/*2.【点与直线】*/
inline int pan_PL_(Point p, Point a, Point b) {
    //【判断点P是否在直线AB上】
    return !dcmp(Cro(p - a, b - a));//PA,AB共线
}
inline Point FootPoint(Point p, Point a, Point b) {
    /
        /【点P到直线AB的垂足】
        Point x = p - a, y = p - b, z = b - a;
    double len1 = Dot(x, z) / Len(z), len2 = -1.0 * Dot(y, z) / Len(z);
    //分别计算AP,BP在AB,BA上的投影
    return a + z * (len1 / (len1 + len2));//点A加上向量AF
}
inline Point Symmetry_PL(Point p, Point a, Point b) {
    //【点P关于直线AB的对称点】
    return p + (FootPoint(p, a, b) - p) * 2;//将PF延长一倍即可
}

/*3.【线与线】*/
inline Point cross_LL(Point a, Point b, Point c, Point d) {
    //【两直线AB,CD的交点】
    Point x = b - a, y = d - c, z = a - c;
    return a + x * (Cro(y, z) / Cro(x, y));//点A加上向量AF
}
inline int pan_cross_L_L(Point a, Point b, Point c, Point d) {
    //【判断直线AB与线段CD是否相交】
    return pan_PL(cross_LL(a, b, c, d), c, d);
    //直线AB与直线CD的交点在线段CD上
}
inline int pan_cross_LL(Point a, Point b, Point c, Point d) {
    //【判断两线段AB,CD是否相交】
    double c1 = Cro(b - a, c - a), c2 = Cro(b - a, d - a);
    double d1 = Cro(d - c, a - c), d2 = Cro(d - c, b - c);
    return dcmp(c1) * dcmp(c2) < 0 && dcmp(d1) * dcmp(d2) < 0;//分别在两侧
}

/*4.【点与多边形】*/
inline int PIP(Point* P, int n, Point a) {
    //【射线法】判断点A是否在任意多边形Poly以内
    int cnt = 0; double tmp;
    for (int i = 1; i <= n; ++i) {
        int j = i < n ? i + 1 : 1;
        if (pan_PL(a, P[i], P[j]))return 2;//点在多边形上
        if (a.y >= min(P[i].y, P[j].y) && a.y < max(P[i].y, P[j].y))
            //纵坐标在该线段两端点之间
            tmp = P[i].x + (a.y - P[i].y) / (P[j].y - P[i].y)
            * (P[j].x - P[i].x), cnt += dcmp(tmp - a.x) > 0;//交点在A右方
    }
    return cnt & 1;//穿过奇数次则在多边形以内
}
inline int judge(Point a, Point L, Point R) {//判断AL是否在AR右边
    return dcmp(Cro(L - a, R - a)) > 0;//必须严格以内
}
inline int PIP_(Point* P, int n, Point a) {
    //【二分法】判断点A是否在凸多边形Poly以内
    //点按逆时针给出
    if (judge(P[1], a, P[2]) || judge(P[1], P[n], a))return 0;
    //在P[1_2]或P[1_n]外
    if (pan_PL(a, P[1], P[2]) || pan_PL(a, P[1], P[n]))return 2;
    //在P[1_2]或P[1_n]上
    int l = 2, r = n - 1;
    while (l < r) {
        //二分找到一个位置pos使得P[1]_A在P[1_pos],P[1_(pos+1)]之间
        int mid = l + r + 1 >> 1;
        if (judge(P[1], P[mid], a))l = mid;
        else r = mid - 1;
    }
    if (judge(P[l], a, P[l + 1]))return 0;//在P[pos_(pos+1)]外
    if (pan_PL(a, P[l], P[l + 1]))return 2;//在P[pos_(pos+1)]上
    return 1;
}

/*5.【线与多边形】*/

/*6.【多边形与多边形】*/
inline int judge_PP(Point* A, int n, Point* B, int m) {
    //【判断多边形A与多边形B是否相离】
    for (int i1 = 1; i1 <= n; ++i1) {
        int j1 = i1 < n ? i1 + 1 : 1;
        for (int i2 = 1; i2 <= m; ++i2) {
            int j2 = i2 < m ? i2 + 1 : 1;
            if (pan_cross_LL(A[i1], A[j1], B[i2], B[j2]))return 0;
            //两线段相交
            if (PIP(B, m, A[i1]) || PIP(A, n, B[i2]))return 0;//点包含在内
        }
    }
    return 1;
}

/*五:【图形面积】*/

/*1.【任意多边形面积】*/
inline double PolyArea(Point* P, int n) {//任意多边形P的面积,逆时针给出点序,或者先求一遍凸包,把点排序
    double S = 0;
    for (int i = 1; i <= n; ++i)S += Cro(P[i], P[i < n ? i + 1 : 1]);
    return S / 2.0;
}

/*2.【圆的面积并】*/

/*3.【三角形面积并】*/

/*六:【凸包】*/

/*1.【求凸包】*/
inline bool cmp1(Point a, Point b) { return a.x == b.x ? a.y < b.y : a.x < b.x; };
//按坐标排序
inline int ConvexHull(Point* P, int n, Point* cp) {
    //【Graham扫描法】求凸包
    sort(P + 1, P + n + 1, cmp1);
    int t = 0;
    for (int i = 1; i <= n; ++i) {//下凸包
        while (t > 1 && dcmp(Cro(cp[t] - cp[t - 1], P[i] - cp[t - 1])) <= 0)--t;
        cp[++t] = P[i];
    }
    int St = t;
    for (int i = n - 1; i >= 1; --i) {//上凸包
        while (t > St && dcmp(Cro(cp[t] - cp[t - 1], P[i] - cp[t - 1])) <= 0)--t;
        cp[++t] = P[i];
    }
    return --t;//要减一
}
/*2.【旋转卡壳】*/

/*3.【半平面交】*/
struct Line {
    Point a, b; double k; Line(Point A = Point(0, 0), Point B = Point(0, 0))
    {
        a = A, b = B, k = atan2(b.y - a.y, b.x - a.x);
    }
    inline bool operator<(const Line& O)const
    {
        return dcmp(k - O.k) ? dcmp(k - O.k) < 0 : judge(O.a, O.b, a);
    }
    //如果角度相等则取左边的
}L[N], Q[N];
inline Point cross(Line L1, Line L2) { return cross_LL(L1.a, L1.b, L2.a, L2.b); }
//获取直线L1,L2的交点
inline int judge(Line L, Point a) { return dcmp(Cro(a - L.a, L.b - L.a)) > 0; }
//判断点a是否在直线L的右边
inline int halfcut(Line* L, int n, Point* P) {//【半平面交】,逆时针,求凸包排点序,再求线Line(p[i - 1], p[i])
    sort(L + 1, L + n + 1); int m = n; n = 0;
    for (int i = 1; i <= m; ++i)if (i == 1 || dcmp(L[i].k - L[i - 1].k))L[++n] = L[i];
    int h = 1, t = 0;
    for (int i = 1; i <= n; ++i) {
        while (h < t && judge(L[i], cross(Q[t], Q[t - 1])))--t; //当队尾两个直线交点不是在直线L[i]上或者左边时就出队
        while (h < t && judge(L[i], cross(Q[h], Q[h + 1])))++h; //当队头两个直线交点不是在直线L[i]上或者左边时就出队
        Q[++t] = L[i];
    }
    while (h < t && judge(Q[h], cross(Q[t], Q[t - 1])))--t;
    while (h < t && judge(Q[t], cross(Q[h], Q[h + 1])))++h;
    n = 0;
    for (int i = h; i <= t; ++i)P[++n] = cross(Q[i], Q[i < t ? i + 1 : h]);
    return n;
}

/*4.【闵可夫斯基和】*/
Point V1[N], V2[N];
inline int Mincowski(Point* P1, int n, Point* P2, int m, Point* V) {
    //【闵可夫斯基和】求两个凸包{P1},{P2}的向量集合{V}={P1+P2}构成的凸包
    for (int i = 1; i <= n; ++i)V1[i] = P1[i < n ? i + 1 : 1] - P1[i];
    for (int i = 1; i <= m; ++i)V2[i] = P2[i < m ? i + 1 : 1] - P2[i];
    int t = 0, i = 1, j = 1; V[++t] = P1[1] + P2[1];
    while (i <= n && j <= m)++t, V[t] = V[t - 1] + (dcmp(Cro(V1[i], V2[j])) > 0 ? V1[i++] : V2[j++]);
    while (i <= n)++t, V[t] = V[t - 1] + V1[i++];
    while (j <= m)++t, V[t] = V[t - 1] + V2[j++];
    return t;
}

/*5.【动态凸包】*/

/*七:【圆】*/

/*1.【三点确定一圆】*/
#define S(a) ((a)*(a))
struct Circle { Point O; double r; Circle(Point P, double R = 0) { O = P, r = R; } };
inline Circle getCircle(Point A, Point B, Point C) {
    //【三点确定一圆】暴力解方程
    double x1 = A.x, y1 = A.y, x2 = B.x, y2 = B.y, x3 = C.x, y3 = C.y;
    double D = ((S(x2) + S(y2) - S(x3) - S(y3)) * (y1 - y2) - (S(x1) + S(y1) -
        S(x2) - S(y2)) * (y2 - y3)) / ((x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2));
    double E = (S(x1) + S(y1) - S(x2) - S(y2) + D * (x1 - x2)) / (y2 - y1);
    double F = -(S(x1) + S(y1) + D * x1 + E * y1);
    return Circle(Point(-D / 2.0, -E / 2.0), sqrt((S(D) + S(E) - 4.0 * F) / 4.0));
}
inline Circle getcircle(Point A, Point B, Point C) {
    //【三点确定一圆】向量垂心法
    Point P1 = (A + B) * 0.5, P2 = (A + C) * 0.5;
    Point O = cross_LL(P1, P1 + Normal(B - A), P2, P2 + Normal(C - A));
    return Circle(O, Len(A - O));
}

/*2.【最小覆盖圆】*/
inline int PIC(Circle C, Point a) { return dcmp(Len(a - C.O) - C.r) <= 0; }
//判断点A是否在圆C内
inline void Random(Point* P, int n)
{
    for (int i = 1; i <= n; ++i)swap(P[i], P[rand() % n + 1]);
}//随机一个排列
inline Circle Min_Circle(Point* P, int n) {//【求点集P的最小覆盖圆】
//  random_shuffle(P+1,P+n+1);
    Random(P, n); Circle C = Circle(P[1], 0);
    for (int i = 2; i <= n; ++i)if (!PIC(C, P[i])) {
        C = Circle(P[i], 0);
        for (int j = 1; j < i; ++j)if (!PIC(C, P[j])) {
            C.O = (P[i] + P[j]) * 0.5, C.r = Len(P[j] - C.O);
            for (int k = 1; k < j; ++k)if (!PIC(C, P[k]))C = getcircle(P[i], P[j], P[k]);
        }
    }
    return C;
}

/*3.【三角剖分】*/
inline double calc(Point A, Point B, Point O, double R) {//【三角剖分】
    if (A == O || B == O)return 0;
    int op = dcmp(Cro(A - O, B - O)) > 0 ? 1 : -1; double ans = 0;
    Point x = A - O, y = B - O;
    int flag1 = dcmp(Len(x) - R) > 0, flag2 = dcmp(Len(y) - R) > 0;
    if (!flag1 && !flag2)ans = Abs(Cro(A - O, B - O)) / 2.0;//两个点都在里面
    else if (flag1 && flag2) {//两个点都在外面
        if (dcmp(dis_PL(O, A, B) - R) >= 0)ans = R * R * Angle(x, y) / 2.0;//完全包含了圆弧
        else {//分三段处理 △+圆弧+△
            if (dcmp(Cro(A - O, B - O)) > 0)swap(A, B);//把A换到左边
            Point F = FootPoint(O, A, B); double lenx = Len(F - O), len = sqrt(R * R - lenx * lenx);
            Point z = turn_P(F - O, Pi / 2.0) * (len / lenx); Point B_ = F + z, A_ = F - z;
            ans = R * R * (Angle(A - O, A_ - O) + Angle(B - O, B_ - O)) / 2.0 + Cro(B_ - O, A_ - O) / 2.0;
        }
    }
    else {//一个点在里面,一个点在外面
        if (flag1)swap(A, B);//使A为里面的点,B为外面的点
        Point F = FootPoint(O, A, B); double lenx = Len(F - O), len = sqrt(R * R - lenx * lenx);
        Point z = turn_P(F - O, Pi / 2.0) * (len / lenx); Point C = dcmp(Cro(A - O, B - O)) > 0 ? F - z : F + z;
        ans = Abs(Cro(A - O, C - O)) / 2.0 + R * R * Angle(C - O, B - O) / 2.0;
    }
    return ans * op;
}

计算机和三维球的交

img

\(S=2\pi∗R∗H\)

\(V=\pi*H^2*\frac{3*R-H}{3}\)

#define sqr(n) ((n) * (n))
const double PI = acos(-1.0);
struct point { double x, y, z; };
struct circle { point o; double r; };
double getlen(point a, point b) {
    return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z));
}
double getCroArea(circle a, circle b) {
    if (a.r > b.r) swap(a, b);
    double dis = getlen(a.o, b.o);
    if (dis + a.r <= b.r) {
        double r = max(a.r, b.r);
        return 4 * PI * r * r;
    } else if (dis < a.r + b.r && dis + a.r > b.r) {
        double angle_cosa = (a.r * a.r + dis * dis - b.r * b.r) / (2 * a.r * dis);
        double angle_cosb = (b.r * b.r + dis * dis - a.r * a.r) / (2 * b.r * dis);
        double len_a = a.r - a.r * angle_cosa, len_b = b.r - b.r * angle_cosb;
        double ans = 4 * PI * (a.r * a.r + b.r * b.r);
        ans -= 2 * PI * (a.r * len_a + b.r * len_b);
        return ans;
    }
    else return 4 * PI * (a.r * a.r + b.r * b.r);
    return 0;
}
double getCroVol(circle o, circle t) {
    if (o.r < t.r) swap(o, t);
    double dis = sqrt(sqr(o.o.x - t.o.x) + sqr(o.o.y - t.o.y) + sqr(o.o.z - t.o.z)), ans = 0;
    if (dis <= o.r - t.r)
        ans = 4.0 / 3 * PI * t.r * t.r * t.r;
    else if (dis < o.r + t.r) {
        double cal = (o.r * o.r + dis * dis -t.r * t.r) / (dis * o.r * 2);
        double h = o.r * (1 - cal);
        ans = PI / 3 * (3 * o.r - h) * h * h;
        cal = (t.r * t.r + dis * dis - o.r * o.r) / (dis * t.r * 2);
        h = t.r * (1 - cal);
        ans += PI / 3 * (3 * t.r - h) * h * h;
    }
    return ans;
}

乱七八糟的公式

莫比乌斯

b(abc) 为 abc的因子个数
\(f(a,b,c) = \sum_i^a\sum_j^b\sum_k^cb(a*b*c) ≡ g(a,b,c) = \sum_{gcd(i,j,k)=1} \left \lfloor \frac{a}{i} \right \rfloor \left \lfloor \frac{b}{j} \right \rfloor \left \lfloor \frac{c}{k} \right \rfloor\)
\(f(a,b)=\sum_i^a\sum_j^bb(a*b) ≡ g(a,b) = \sum_{gcd(i,j)=1}\left \lfloor \frac{a}{i} \right \rfloor \left \lfloor \frac{b}{j} \right \rfloor\)

图论

最短路

迪杰斯特拉

无法判负边,负环

贝尔曼

可以判负环, 用spfa, 最坏 O(NM), 可以判负环进行差分约束

bfs判负环

bool check(double mid) {
    stack<int> st; rep(i, 1, n) dis[i] = dep[i] = 0, v[i] = 1, st.push(i);
    while (!st.empty()) {
        int x = st.top(); st.pop(); v[x] = 0;
        for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
            if (dis[y] <= dis[x] + co[i]) continue;
            dis[y] = dis[x] + co[i]; dep[y] = dep[x] + 1;
            if (dep[y] >= n) return 1; /*有负环*/ if (!v[y]) st.push(y), v[y] = 1;
        }
    } return 0;
}

dfs 判负环

bool dfs(int x) {
    v[x] = 1;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
        if (dis[y] > dis[x] + co[i]) {
            dis[y] = dis[x] + co[i];
            if (v[y] || dfs(y)) return 1; //有负环
        }
    return v[x] = 0;
}

弗洛伊德

rep (k, 1, n) rep (i, 1, n) rep (j, 1, n) umin(d[i][j], d[i][k] + d[k][j]);

最小生成树

kruskal

prim

次小严格生成树

找到非生成边(x, y)去替换树上x到y路径的最大边,费用差值最小,就是次小和最小生成树的差值

const int N = 1e5 + 5, M = 3e5 + 5;
struct STFrom {
    int f[N][20], dep[N], lg[N], t;//N为节点的数量
    ll d[N][20], b[N][20];
    vector<PII> *h;
    void init(int n, vector<PII> *H) {
        t = log2(n - 1) + 1; h = H; lg[0] = -1;
        rep(i, 1, n) dep[i] = 0, lg[i] = lg[i >> 1] + 1;
    }
    void bfs(int s) {
        queue<int> q; q.push(s); dep[s] = 1;
        rep(i, 0, t) f[s][i] = 0, d[s][i] = b[s][i] = 0;
        while (!q.empty()) {
            int x = q.front(); q.pop();
            for (auto &y : h[x]) {
                if (dep[y.fi]) continue;
                dep[y.fi] = dep[x] + 1; f[y.fi][0] = x; q.push(y.fi);
                d[y.fi][0] = y.se; b[y.fi][0] = -2e18;
                for (int j = 1; j <= t; ++j) {
                    f[y.fi][j] = f[f[y.fi][j - 1]][j - 1];
                    if (d[f[y.fi][j - 1]][j - 1] > d[y.fi][j - 1])
                        b[y.fi][j] = max(d[y.fi][j - 1], b[f[y.fi][j - 1]][j - 1]),
                        d[y.fi][j] = d[f[y.fi][j - 1]][j - 1];
                    else if (d[f[y.fi][j - 1]][j - 1] == d[y.fi][j - 1])
                        b[y.fi][j] = max(b[y.fi][j - 1], b[f[y.fi][j - 1]][j - 1]),
                        d[y.fi][j] = d[y.fi][j - 1];
                    else b[y.fi][j] = max(b[y.fi][j - 1], d[f[y.fi][j - 1]][j - 1]),
                        d[y.fi][j] = d[y.fi][j - 1];
                }
            }
        }
    }
    void work(PLL& ans, int y, int i) {
        if (d[y][i] > ans.fi) ans.se = max(ans.fi, b[y][i]), ans.fi = d[y][i];
        else if (d[y][i] == ans.fi) umax(ans.se, b[y][i]);
        else umax(ans.se, d[y][i]);
    }
    PLL ask(int x, int y) {
        PLL ans = {-2e18, -2e18};
        if (dep[x] > dep[y]) swap(x, y);
        for(int k = dep[y] - dep[x], i = lg[k]; ~i; --i) if (k >> i & 1)
            work(ans, y, i), y = f[y][i];k
        if (x == y) return ans;
        per(i, lg[dep[y]], 0) if (f[x][i] ^ f[y][i]) {
            work(ans, x, i); work(ans, y, i);
            x = f[x][i], y = f[y][i];
        }
        work(ans, x, 0); work(ans, y, 0);
        return ans;
    }
} ST;
struct edge {int x, y; ll c; bool f = 0; } e[M];
int n, m, _, k, cas, f[N];
ll res, ans = 2e18;
vector<PII> h[N];
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
int main() {
    IOS; cin >> n >> m;
    rep (i, 1, m) cin >> e[i].x >> e[i].y >> e[i].c;
    sort(e + 1 , e + 1 + m, [](edge& a, edge& b) { return a.c < b.c; });
    rep (i, 1, n) f[i] = i;
    rep (i, 1, m) {
        int x = find(e[i].x), y = find(e[i].y);
        if (x == y) continue;
        res += e[i].c, e[i].f = 1; f[y] = x;
        h[e[i].x].pb(e[i].y, e[i].c); h[e[i].y].pb(e[i].x, e[i].c);
    }
    ST.init(n, h); ST.bfs(1);
    rep (i, 1, m) if (!e[i].f) {
        auto cur = ST.ask(e[i].x, e[i].y);
        if (cur.fi < e[i].c) umin(ans, res - cur.fi + e[i].c);
        else if(cur.se != -2e18 && cur.se < e[i].c) umin(ans, res - cur.se + e[i].c); 
    }
    return cout << ans, 0;
}

最小树形图朱刘算法O(NM)

struct Edge { int x, y, w; } e[N];
ll zhuLiu (int root, int n, vector<Edge>& e) { //从0开始,下标从1开始传n + 1
    ll ans = 0; VI in(n), pre(n); //in为最小入边权, pre为其对应的起点
    while (true) {
        for (auto &i : in) i = INF;
        //可以添加虚点,连向每个点w=inf,ans>=2*inf无解,在添加最小边的时
        //选了虚边,另一端是实际源点,但点的编号已经改变,只能记录用了哪条边,最后还原点
        /* if (e[i].w < in[e[i].y] && e[i].x != e[i].y) {
                pre[e[i].y] = e[i].x, in[e[i].y] = e[i].w;*/
                /*if (e[i].x == root) pos = i;//记录连接实际root的虚边
            } */
        for (auto &i:e) if (i.w < in[i.y] && i.x != i.y) pre[i.y] = i.x, in[i.y] = i.w;
        rep (i, 0, n - 1) if (i != root && in[i] == INF) return -1;
        int cnt = in[root] = 0; VI idx(n, -1), v(n, -1);
        rep (i, 0, n - 1) {//标记每个环
            int y = i; ans += in[i];//记录权值
            while (v[y] != i && idx[y] == -1 && y != root) v[y] = i, y = pre[y];
            if (y != root && idx[y] == -1) {
                for(int x = pre[y]; x != y; x = pre[x]) idx[x] = cnt;
                idx[y] = cnt++;
            }
        } if (cnt == 0) break;
        for (auto &i : idx) if (i == -1) i = cnt++;
        for (auto &i : e) {
            if (idx[i.x] != idx[i.y]) i.w -= in[i.y];
            i.x = idx[i.x], i.y = idx[i.y];
        } n = cnt; root = idx[root];
    } return ans;
}

树的重心 (最多两个且相连)

int mx; VI gra;
void dfs(int x, int fa) {
    siz[x] = 1; int max_size = 0;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
        if (y == fa) continue;
        dfs(y); siz[x] += siz[y]; umax(max_size, siz[y]);
    }
    umax(max_size, n - siz[x]);
    if (mx > max_size) mx = max_size, gra.resize(0), gra.pb(x);
    else if (mx == max_size) gra.pb(x);
}

差分约束

记得在加完题目约束之后,别忘了i和i+1的默认约束,且别忘了超级源点让图联通

树的直径(可以有很多条)

两次dfs求出最远的两个点 (保留路径方便, 边权为非负)

void dfs(int u, int fa) {
    for (int i = h[u], y = to[i]; i; y = to[i = ne[i]]) {
        if (fa != y) continue;
        f[y] = u; b[y] = i; d[y] = d[u] + co[i];
        if (d[0] < d[y]) d[0] = d[y], f[0] = y;
        dfs(y, x);
    }
}
void work(int& p, int& q) {
    d[0] = -N; d[1] = 0; dfs(1, 0); p = f[0];
    d[0] = -N; d[p] = 0; dfs(p, 0); q = f[0];
}

dp(边权任意, 保留路径困难)

void dpfind(int u, int fa, int& ans) {
    for (int i = h[u]; i; i = ne[i]) {
        int y = to[i];
        if (fa != y) continue;
        dpfind(y, u, ans);
        umax(ans, d[u] + d[y] + co[i]), umax(d[u], d[y] + co[i]);
    } umax(d[u], 0);
}

最近公共祖先

struct STFrom {
    int f[N][20], dep[N], lg[N], t;//N为节点的数量
    int *h, *ne, *to;
    void init(int n, int* H, int* Ne, int* To) {
        t = log2(n - 1) + 1; h = H, ne = Ne, to = To; lg[0] = -1;
        rep(i, 1, n) dep[i] = 0, lg[i] = lg[i >> 1] + 1;
    }
    void bfs(int s) {
        queue<int> q; q.push(s); dep[s] = 1;
        rep(i, 0, t) f[s][i] = 0;
        while (!q.empty()) {
            int x = q.front(); q.pop();
            for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
                if (dep[y]) continue;
                dep[y] = dep[x] + 1; f[y][0] = x; q.push(y);
                for (int j = 1; j <= t; ++j) f[y][j] = f[f[y][j - 1]][j - 1];
            }
        }
    }
    int lca(int x, int y) {
        if (dep[x] > dep[y]) swap(x, y);
        for (int k = dep[y] - dep[x]; ~lg[k]; k ^= 1 << lg[k]) y = f[y][lg[k]];
        if (x == y) return x;
        per(i, lg[dep[y]], 0) if (f[x][i] ^ f[y][i]) x = f[x][i], y = f[y][i];
        return f[x][0];
    }
    int dist(int x, int y) { return dep[x] + dep[y] - (dep[lca(x, y)]<<1); }
} ST;

基环树

所有问题都分为, 在同一颗子树和环上不同子树来处理,
且处理子树, 莫走到其他子树上, 直接从环上节点作为子树根, 然后再也不走到环上

无向基环树

pair<int, ll> cir[N]; //<环上节点编号, 从第个点到此点的距离>
bool v[N]; //是否在环上
int dfsc(int u, int bian) {
    idx[u] = idcnt; int s = 0;
    for (int i = h[u]; i; i = ne[i]) {
        int y = to[i];
        if (bian == (i ^ 1) || v[y]) continue;
        if (!idx[y]) s = max(s, dfsc(y, i));
        else cir[idc[s = u] = ++cid] = u, v[u] = 1;
    }
    return s;
}
void work(int u) {
    cid = 0; ++idcnt;
    for (u = a[dfsc(u, 0)], ls = 0;u != cir[1]; ls = idc[u], u = a[u]) 
        cir[idc[u] = ++cid] = { u, cir[ls].se + 1 }, v[u] = 1;
    sizc[idcnt] = cid;
}

无向基环树森林直径和

rep (i, 1, n) if (!dfn[i]) ans += work(i)
ll work(int u) {
    ll ans = cid = 0; tarjan(u, 0);//找出当前基环树的环
    rep (i, 1, cid) {
        cir[i + cid] = cir[i]; cir[i + cid].se += cir[cid].se + cir[0].se;
        dpfind(cir[i].fi, 0, ans); //dp找环上节点子树的直径
    }
    int tail = -1, head = 0;; cid <<= 1;
    q[++tail] = { 1, dis[cir[1].fi] };
    rep(i, 2, cid) {
        while (i - q[head].fi >= cid >> 1) ++head;
        ans = max(ans, dis[cir[i].fi] + cir[i].se + q[head].se);
        ll w = dis[cir[i].fi] - cir[i].se;
        while (head <= tail && q[tail].se <= w) --tail;
        q[++tail] = { i, w };
    } return ans;
}

内向树森林

要反向建边, 才能从环上子树走向环

树链剖分

基础

struct BIT {
    static const int N = 1e5 + 5;
    struct node { int l, r, len; ll val, tag; } tr[N << 2];
    void push_up(int rt) { tr[rt].val = (tr[rt << 1 | 1].val + tr[rt << 1].val) % mod; }
    void push_down(int rt) {
        if (!tr[rt].tag) return;
        tr[rt << 1].val = (tr[rt << 1].val + tr[rt << 1].len * tr[rt].tag % mod) % mod;
        tr[rt<<1|1].val=(tr[rt<<1|1].val+tr[rt<<1|1].len*tr[rt].tag%mod)%mod;
        tr[rt << 1].tag = (tr[rt << 1].tag + tr[rt].tag) % mod;
        tr[rt << 1 | 1].tag = (tr[rt << 1 | 1].tag + tr[rt].tag) % mod; tr[rt].tag = 0;
    }
    void build(int rt, int l, int r, int* a) {
        tr[rt] = { l, r, r - l + 1, 0 };
        if (l == r) { tr[rt].val = a[l] % mod; return; }
        int mid = l + r >> 1;
        build(rt << 1, l, mid, a); build(rt << 1 | 1, mid + 1, r, a); push_up(rt);
    }
    void change(int rt, int l, int r, ll k) {
        if (tr[rt].l >= l && tr[rt].r <= r) {
            tr[rt].val = (tr[rt].val + tr[rt].len * k % mod) % mod;
            tr[rt].tag = (k + tr[rt].tag) % mod; return;
        }
        push_down(rt);
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (mid >= l) change(rt << 1, l, r, k);
        if (mid < r) change(rt << 1 | 1, l, r, k);
        push_up(rt);
    }
    ll ask(int rt, int l, int r) {
        if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
        push_down(rt);
        int mid = tr[rt].l + tr[rt].r >> 1;
        ll ans = mid >= l ? ask(rt << 1, l, r) : 0;
        if (mid < r) ans = (ans + ask(rt << 1 | 1, l, r)) % mod;
        push_up(rt); return ans;
    }
} bit;
VI h[N];
int a[N], na[N], dep[N], hson[N], siz[N], fa[N], top[N], dfn[N], df;
void findhson(int x, int f) {
    dep[x] = dep[f] + 1; siz[x] = 1; fa[x] = f;
    int mx = 0;
    for (auto y : h[x]) {
        if (y == f) continue; findhson(y, x);
        if (mx < siz[y]) mx = siz[y], hson[x] = y; siz[x] += siz[y];
    }
}
void dfs(int x, int f) {
    na[dfn[x] = ++df] = a[x]; top[x] = f;
    if (!hson[x]) return;
    dfs(hson[x], f);
    for (auto y : h[x]) if (y != fa[x] && y != hson[x]) dfs(y, y);
}
void solve1() { //将树从 x 到 y 结点最短路径上所有节点的值都加上 z
    int x, y, z; cin >> x >> y >> z; z %= mod;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        bit.change(1, dfn[top[x]], dfn[x], z); x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    bit.change(1, dfn[x], dfn[y], z);
}
ll solve2() { //求树从 x 到 y 结点最短路径上所有节点的值之和
    int x, y; cin >> x >> y; ll ans = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        ans = (ans + bit.ask(1, dfn[top[x]], dfn[x])) % mod; x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    return ((ans + bit.ask(1, dfn[x], dfn[y])) % mod + mod) % mod;
}
void solve3() { //将以 x 为根节点的子树内所有节点值都加上 z
    int x, z; cin >> x >> z; z %= mod;
    bit.change(1, dfn[x], dfn[x] + siz[x] - 1, z);
}
ll solve4() { //求以 x 为根节点的子树内所有节点值之和
    int x; cin >> x;
    return (bit.ask(1, dfn[x], dfn[x] + siz[x] - 1) + mod) % mod;
}
int main() {
    IOS; cin >> n >> m >> s >> mod;
    rep(i, 1, n) cin >> a[i];
    rep(i, 2, n) { int u, v; cin >> u >> v; h[u].pb(v); h[v].pb(u); }
    findhson(s, 0); dfs(s, s); bit.build(1, 1, df, na);
    rep(i, 1, m) {
        int op; cin >> op;
        switch (op) {
            case 1: solve1(); break;
            case 2: cout << solve2() << '\n'; break;
            case 3: solve3(); break;
            case 4: cout << solve4() << '\n'; break;
        }
    } return 0;
}

点分树

struct STFrom { ... } ST; //LCA的ST表
struct DTTree {
    static const int N = 1e5 + 5;
    struct node { int fa; } t[N];
    int mxsz[N], csz[N], rt, siz[N];
    int* h, * ne, * to;
    bool v[N];
    void init(int n, int* H, int* Ne, int* To) {
        h = H, ne = Ne, to = To; rt = 0;
        rep(i, 1, n) v[i] = t[i].fa = 0;
    }
    void dfscenter(int x, int f, int sum) {
        csz[x] = 1; mxsz[x] = 0;
        for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) if (y != f && !v[y]) {
            dfscenter(y, x, sum); csz[x] += csz[y]; mxsz[x] = max(mxsz[x], csz[y]);
        } umax(mxsz[x], sum - csz[x]);
        if (!rt || mxsz[x] < mxsz[rt]) rt = x;
    }
    void dfs(int x, int sum) {
        v[x] = 1; siz[x] = sum; rt = 0;
        for (int i = h[x], y = to[i]; i; y = to[i = ne[i]], rt = 0) if (!v[y]) {
            int yz = csz[y] < csz[x] ? csz[y] : sum - csz[x];
            dfscenter(y, x, yz); t[rt].fa = x; dfs(rt, yz);
        }
    }
} DT;
struct DyBIT {
    static const int n = 2e5 + 5;
    struct Node { int l, r, val, lson, rson;} tr[N * 20];
    int root[N], tot;
    void init(int n) { rep(i, 1, n) root[i] = 0; tot = 0; }
    int newNode(){++tot; tr[tot].lson = tr[tot].rson = tr[tot].val = 0; return tot;}
    void change(int& rt, int l, int r, int p, int k) {
        if (!rt) rt = newNode(), tr[rt].l = l, tr[rt].r = r;
        tr[rt].val += k; if (l == r) return;
        int mid = l + r >> 1;
        if (p <= mid) change(tr[rt].lson, l, mid, p, k);
        else change(tr[rt].rson, mid + 1, r, p, k);
    }
    int ask(int rt, int l, int r, int L, int R) {
        if (!rt || L > R) return 0;
        if (l >= L && r <= R) return tr[rt].val;
        int mid = l + r >> 1;
        int ans = L <= mid ? ask(tr[rt].lson, l, mid, L, R) : 0;
        if (R > mid) ans += ask(tr[rt].rson, mid + 1, r, L, R);
        return ans;
    }
} bita, bitb;
int w[N];
void change(int x, int k) {
    for (int p = x, fa; p; p = fa) {
        fa = DT.t[p].fa; bita.change(bita.root[p], 0, DT.siz[p], ST.dist(p, x), k);
        if (fa) bitb.change(bitb.root[p], 0, DT.siz[fa], ST.dist(fa, x), k);
    }
}
int ask(int x, int k) {
    int ans = 0;
    for(int u=x,p=0,d=k-ST.dist(u,x);u;p=u,d=k-ST.dist(u=DT.t[u].fa,x))if(d >= 0){
        ans += bita.ask(bita.root[u], 0, DT.siz[u], 0, d);
        if (p) ans -= bitb.ask(bitb.root[p], 0, DT.siz[u], 0, d);
    } return ans;
}
int main() {
    IOS; while (cin >> n >> m) {
        rep(i, 1, n) cin >> w[i], h[i] = 0; tot = 0;
        rep(i, 2, n) {int u, v; cin >> u >> v; add(u, v); add(v, u);}
        bita.init(n); bitb.init(n);
        ST.init(n, h, ne, to); ST.bfs(1);
        DT.init(n, h, ne, to); DT.dfscenter(1, 0, n); DT.dfs(DT.rt, n);
        rep(i, 1, n) change(i, w[i]); //int las = 0;
        rep(i, 1, m) {
            string op; int x, y; cin >> op >> x >> y;//x ^= las, y ^= las;
            if (op[0] == '?') cout << ask(x, y) << '\n';
            else change(x, y - w[x]), w[x] = y;
        }
    } return 0;
}

无向图连通性

e-dcc缩点, 求桥

int c[N], ecnt;
vector<vector<int>> ecc;
bool edge[M << 1];
void tarjan(int x, int bian) {
    dfn[st[++top] = x] = low[x] = ++df;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
        if (!dfn[y]) {
            tarjan(y, i ^ 1); umin(low[x], low[y]);
            if (dfn[x] < low[y]) edge[i] = edge[i ^ 1] = 1;
        }
        else if (i ^ bian) low[x] = min(low[x], dfn[y]);
    if (low[x] == dfn[x]) {
        ++ecnt; ecc.pb(VI()); int y;
        do { c[y = st[top--]] = ecnt; ecc.back().pb(y); } while (y != x);
    }
}
rep (i, 1, n) if (!dfn[i]) top = 0, tarjan(i, 0);//遍历
rep (i, 2, tot) { //可能有重边, 新建缩点图
    int x = to[i ^ 1], y = to[i];
    if (c[x] == c[y]) continue;
    add_c(c[x], c[y]);
}

v-dcc, 求割点

int newid[N], pre[N], num, c[N], bl[M]; //bl[i], 原先的边属于哪一个vcc
vector<vector<int>> dcc;
bool cut[N];
void tarjan(int x) {
    dfn[x] = low[x] = ++df;
    if (!h[x]) { dcc.pb(vector<int>(1, x)); return; }
    st[++top] = x; int cnt = 0;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
        if (!dfn[y]) {
            tarjan(y); low[x] = min(low[x], low[y]);
            if (dfn[x] <= low[y]) {
                ++cnt; int z;
                dcc.pb(vector<int>());
                if (x != root || cnt > 1) cut[x] = 1;
                do dcc.back().pb(z = st[top--]); while (z != y);
                dcc.back().pb(x);
            }
        }
        else umin(low[x], dfn[y]);
}
rep (i, 1, n) if (!dfn[i]) top = 0, root = i, tarjan(i);//遍历
num = dcc.size();
rep (i, 1, n) if (cut[i]) newid[i] = ++num, pre[num] = i; //切点新编号
per (i, dcc.size(), 1)
    for (int &j : dcc[i - 1]) {
        c[j] = i;
        if (cut[j]) add_c(newid[j], i), add_c(i, newid[j]);
        for (int k = h[j]; k; k = ne[k]) if (c[to[k]] == i) bl[k >> 1] = i;
    }

欧拉回路

bool eulerc(int s) {
    for (auto& i : edge)
        if (i.size() & 1) return 0; //存在入度为奇数, 无欧拉回路
        else sort(all(i), greater<PII>()); //把边按照边的权值排序(使得欧拉回路输出字典序最小)
    top = t = 0; st[++top] = 0; st[++top] = s;
    while (top) {
        int x = st[top];
        while (!edge[x].empty() && vis[edge[x].back().fi]) edge[x].pop_back();
        if (!edge[x].empty()) {
            st[++top] = edge[x].back().fi;
            st[++top] = edge[x].back().se;
            vis[st[top - 1]] = 1;
            //标记边(编号)使用过, 另一次访问次边时边的另一节点,
            //如果边可以来回走一次, 那就把加入反边编号
            edge[x].pop_back();
        }
        else ans[++t] = st[(--top)--];
    }
    return 1;
}

仙人掌(连通图,一条边最多在一个环上) 圆方树

圆方树,圆点是原来的点,方点代表一个环(拆环),环上的点向这个环对应的方点连边

void solve(int x, int y, int c) { //再次点双中, 以x为起点, 以x->y方向遍历次点双
    //sum[i]表示从x->y方向到i,i距离x的距离,sum[vcnt+n]是这个点双的环的大小
    sum[++vcnt + n] = sum[y] = c;
    for (int i = y; i != x; i = fa[i]) sum[fa[i]] = sum[vcnt + n] += dist[i];
    sum[x] = 0;
    for (int i = y; i != fa[x]; i = fa[i]) { //点双上园点向方点连边
        int c = min(sum[i], sum[vcnt + n] - sum[i]);
        add_c(i, vcnt + n, c); add_c(vcnt + n, i, c);
    }
}
void tarjan(int x) {
    dfn[x] = low[x] = ++df;
    if (!h[x]) return; st[++top] = x;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
        if (!dfn[y]) {
            dist[y] = co[i]; fa[y] = x;
            tarjan(y); umin(low[x], low[y]);
            if (dfn[x] <= low[y]) { //找到一个点双
                int z = st[top], c;
                while (y != st[top--]);
                for(int j = h[z]; j; j = ne[j]) if(to[j] == x) { c = co[j]; break; }
                solve(x, z, c);
            }
        }
        else umin(low[x], dfn[y]);
}

有向图的连通性

scc缩点

int c[N], scnt;
vector<VI> scc;
bool inst[N]; //是否在栈中
void tarjan(int x) {
    dfn[x] = low[x] = ++df; inst[st[++top] = x] = 1;
    for (int i = h[x], y = to[i] ; i; y = to[i = ne[i]])
        if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
        else if (inst[y]) low[x] = min(low[x], dfn[y]);
    if (low[x] == dfn[x]) {
        ++scnt; scc.pb(VI());
        for (int y = 0; y ^ x; y = st[top--])
            inst[st[top]] = 0, c[st[top]] = scnt, scc.back().pb(st[top]);
    }
}
rep (i, 1, n)
    for (int k = h[i], y; k; k = ne[k]) {
        if (c[i] == c[y = to[k]]) continue;
        add_c(c[i], c[y]);
    }

2-SAT问题

int n, m, _, k, h[N][N];
int dfn[N], low[N], df, st[N], top;
int c[N], scnt, opp[N], val[N];
bool inst[N];
pair<int, int> t[N];

void tarjan(int x) {
    dfn[x] = low[x] = ++df, inst[st[++top] = x] = 1;
    for (int y = 1; y <= m; ++y) if (h[x][y])
        if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
        else if (inst[y]) low[x] = min(low[x], dfn[y]);
    if (low[x] == dfn[x]) {
        ++scnt; int y;
        do inst[y = st[top--]] = 0, c[y] = scnt; while (y != x);
    }
}

void _print(int c) {
    int h = c / 60, m = c % 60;
    if (h < 10) printf("0%d:", h);
    else printf("%d:", h);
    if (m < 10) printf("0%d", m);
    else printf("%d", m);
}

void print(int x) { _print(t[x].first); printf(" "); _print(t[x].second); puts(""); }

int main() {
    scanf("%d", &n); bool f = 1; m = n << 1;
    for (int i = 1; i <= n; ++i) {
        int a, b, c, d, e; opp[i] = i + n, opp[i + n] = i;
        scanf("%d:%d %d:%d %d", &a, &b, &c, &d, &e);
        t[i] = { a * 60 + b, a * 60 + b + e }; t[i + n] = { c * 60 + d - e, c * 60 + d };
        if (t[i].first > t[i].second || t[i + n].first > t[i + n].second) f = 0;
    }
    for (int i = 1; i < m; ++i) for (int j = i + 1; j <= m; ++j)
        if (i == j || j == i + n) continue;
        else if (t[i].second > t[j].first && t[i].first < t[j].second) h[i][opp[j]] = h[j][opp[i]] = 1;
    for (int i = 1; i <= m; ++i) if (!dfn[i]) top = 0, tarjan(i);
    for (int i = 1; i <= n; ++i) if (c[i] == c[i + n]) return puts("NO"), 0;
    puts("YES");
    for (int i = 1; i <= m; ++i) val[i] = c[i] > c[opp[i]];
    for (int i = 1; i <= n; ++i) print(val[i] ? opp[i] : i);
    return 0;
}
第 i 对情侣需要 Di 分钟完成这个仪式,即必须选择 Si∼Si+Di 或 Ti−Di∼Ti 两个时间段之一。

牧师想知道他能否满足每场婚礼的要求,即给每对情侣安排Si∼Si+Di 或 Ti−Di∼Ti,使得这些仪式的时间段不重叠。

rep (i, 1, n) opp[i] = n + i, opp[n + i] = i;
rep (i, 1, n << 1) if (!dfn[i]) top = 0, tarjan(i);
rep (i, 1, n) if (c[i] == c[i + n]) { puts("-1"); break; }
rep (i, 1, n << 1) val[i] = c[i] > c[opp[i]];
// val[i] == 0, 选择 i, val[i] == 1, 选择 i + n

SCC求割点割边

Lengauer-Tarjan(支配树)

VI ha[N], hb[N], hc[N];//正边,反边,被半支配点
int idx[N], dfn[N], df, fa[N], anc[N], best[N];
int idom[N], sdom[N];
void dfs(int x) {
    idx[dfn[x] = ++df] = x;
    for (auto &y : ha[x]) if (!dfn[y]) dfs(y), fa[y] = x;
}
int find(int x) {
    if (x == anc[x]) return x;
    int y = find(anc[x]);
    if (dfn[sdom[best[anc[x]]]] < dfn[sdom[best[x]]]) best[x] = best[anc[x]];
    return anc[x] = y;
}
int eval(int x) { find(x); return best[x]; }
void tarjan() {
    per (y, df, 2) {
        int x = idx[y];
        for (auto &z : hb[x]) {
            if (!dfn[z]) continue; find(z);
            if (dfn[sdom[best[z]]] < dfn[sdom[x]]) sdom[x] = sdom[best[z]];
        }
        hc[sdom[x]].pb(x);
        x = anc[x] = fa[x];
        for (auto &z : hc[x]) {
            int u = eval(z);
            idom[z] = sdom[u] == x ? x : u;//最近支配点
        }
    }
    rep (i, 2, df) {
        int x = idx[i];
        if (idom[x] != sdom[x]) idom[x] = idom[idom[x]];
    }
}
rep (i, 1, n) sdom[i] = anc[i] = best[i] = i;
dfs(0); tarjan();

给定s,t的必经边、点

拓扑排序求,fs[x]起点s到x的路径数,ft[x]x到终点t的路径数,双\三hash存储(爆ll)
fs[x]ft[y]=fs[t],(x,y)必经边;fs[x]ft[x]=fs[t],x是必经点

二分图的匹配

最大匹配=最小点覆盖=n-最大独立集大小,最大团等于补图的最大独立集
有向无环图最小路径点覆盖, (路径可覆盖则跑一遍传递闭包), 将每个点拆成入点和出点, n - 拆点二分图最大匹配

二分图的判定

染色法

最大匹配(NN+NM)

rep (i, 1, n) { rep (j, 1, n) v[j] = 0; m += dfs(i); }
bool dfs(int x) {
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
        if (v[y]) continue; v[y] = 1;
        if (!match[y] || dfs(match[y])) return match[y] = x, 1;
    } return 0;
}

KM带权(最大)匹配\((N^3)\)

int w[N][N], la[N], lb[N], match[N], delta; //边权,左、右顶表
bool va[N], vb[N]; //左右点是否在交错树中
bool dfs(int x) {
    va[x] = 1;
    rep (y, 1, n) if (!vb[y])
        if (la[x] + lb[y] == w[x][y]) {
            vb[y] = 1;
            if (!match[y] || dfs(match[y])) { match[y] = x; return 1; }
        }
    return 0;
}
int KM() {
    rep (i, 1, n) {
        la[i] = -inf; lb[i] = 0;
        rep (j, 1, n) umax(la[i], w[i][j]);
    }
    rep (i, 1, n)
        while (1) {
            rep (i, 1, n) va[i] = vb[i] = 0;/*;*/delta = inf; if (dfs(i)) break;
            rep (x, 1, n) if (va[x]) rep (y, 1, n) if (!vb[y]) delta = min(delta, la[x] + lb[y] - w[x][y]); 
            rep (j, 1, n) la[j] -= va[j] ? delta : 0, lb[j] += vb[j] ? delta : 0;
        }
    ll ans = 0; rep (i, 1, n) ans += w[match[i]][i]; return ans;
}

一般图(带花树)O(n^2m)

int que[M], ql, qr, pre[N], tim = 0;
int match[N], f[N], tp[N], tic[N];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
int lca(int x,int y) {
	for (++tim; ; swap(x,y)) if (x) {
		x = find(x);
		if (tic[x] == tim) return x;
        else tic[x] = tim, x = pre[match[x]];
	}
}
void shrink(int x, int y, int p) {
	while (find(x) != p) {
		pre[x] = y, y = match[x];
		if (tp[y] == 2) tp[y] = 1, que[++qr] = y;
		if (find(x) == x) f[x] = p;
		if (find(y) == y) f[y] = p;
		x = pre[y];
	}
}
bool aug(int s) {
	rep (i, 0, n) f[i] = i, tp[i] = pre[i] = 0;
	tp[que[ql = qr = 1] = s] = 1; // 1: type A ; 2: type B
	for (int t = 0; ql <= qr; ) {
		int x = que[ql++];
		for (int i = h[x], v = to[i]; i; i = ne[i], v = to[i])
			if (find(v) == find(x) || tp[v] == 2) continue; 
			else if (!tp[v]) {
				tp[v] = 2, pre[v] = x;
				if (!match[v]) {
					for (int now = v, last, tmp; now; now = last) {
						last = match[tmp = pre[now]];
						match[now] = tmp, match[tmp] = now;
					} return true;
				} 
				tp[match[v]] = 1, que[++qr] = match[v];
			} else if (tp[v] == 1) {
				int l = lca(x,v); shrink(x,v,l), shrink(v,x,l);
			}
    } return false;
}
int main() {
	read(n); int x, y;
	while (~scanf("%d%d", &x, &y)) add(x,y), add(y,x);
	int ans = 0; rep (i, 1, n) ans += (!match[i] && aug(i));
	write(ans << 1); puts("");//ans表示有几对
	rep (i, 1, n) if (match[i] > i) write(i), putchar(' '), write(match[i]), puts("");
	return 0;
}

网络流

最大流

Edmonds-Karp \(O(nm^2)\)

const int N = 2010, M = 20010, inf = 1 << 30;
int h[N], to[M], ne[M], co[M], tot;
int v[N], incf[N], pre[N], s, t, maxflow;
void add(int u, int v, int c) {
    ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c;
    ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = 0;
}
while (cin >> n >> m) {
    rep (i, 1, n) h[i] = 0;
    s = 1, t = n; tot = 1; maxflow = 0;
    rep (i, 1, m) {
        int u, v, c; cin >> u >> v >> c;
        add(u, v, c);
    }
    while (bfs()) update();
    cout << maxflow << '\n';
}
bool bfs () {
    rep (i, 1, n) v[i] = 0;
    queue<int> q; q.push(s); v[s] = 1;
    incf[s] = inf; //增广路上各边的最小剩余容量
    while (!q.empty()) {
        int x = q.front(); q.pop(); v[x] = 0;
        for (int i = h[x]; i; i = ne[i]) {
            if (!co[i]) continue;
            int y = to[i];
            if (v[y]) continue;
            incf[y] = min(incf[x], co[i]);
            pre[y] = i; q.push(y); v[y] = 1;
            if (y == t) return 1;
        }
    }
    return 0;
}
void update() {
    int x = t;
    while (x != s) {
        int i = pre[x];
        co[i] -= incf[t]; co[i ^ 1] += incf[t];
        x = to[i ^ 1];
    }
    maxflow += incf[t];
}

Dinic \(O(n^2m)\)

const int N = 5e4 + 5, M = 3e5 + 5, inf = 1 << 30;
int d[N], s, t, maxflow;
tot = 1;
int flow = 0;
while (bfs()) while (flow = dinic(s, inf)) maxflow += flow;
bool bfs() {
    memset(d, 0, sizeof d); memcpy(now, h, sizeof h);
    queue<int> q; q.push(s); d[s] = 1;
    while (!q.empty()) {
        int x = q.front(); q.pop();
        for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) if (co[i] && !d[y]) {
            d[y] = d[x] + 1; q.push(y);
            if (y == t) return 1;
        }
    }
    return 0;
}
int dinic(int x, int flow) {
    if (x == t) return flow;
    int rest = flow, k;
    for (int &i = now[x], y = to[i]; i && rest; y = to[i = ne[i]]) if (co[i] && d[y] == d[x] + 1)
        if (!(k = dinic(y, min(rest, co[i])))) d[y] = 0;
        else co[i] -= k, co[i ^ 1] += k, rest -= k;
    return flow - rest;
}

最大流关键边

即参与网络存在\(s\)\(u\), \(v\)\(t\)且边\((u, v)\)无流量, 则边\((u, v)\)为关键边, 即从\(s,t\)求bfs可达点

bool vs[N], vt[N];
void dfs(int x, bool *v, bool k) {
    v[x] = 1;
    for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
        if (co[i ^ k] && !v[y]) dfs(y, v, k);
}
dfs(s, vs, 0); dfs(t, vt, 1);
rep (i, 1, m) k += !co[i << 1] && vs[to[i << 1 | 1]] && vt[to[i << 1]];

费用流

const int N = 5e3 + 5, M = 4e4 + 5, inf = 1 << 30;
int n, m, _, k;
int h[N], to[M], ne[M], co[M], ed[M], tot;
int v[N], incf[N], pre[N], s, t, maxflow, d[N], ans;
void add(int u, int v, int e, int c) {
    ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c, ed[tot] = e;
    ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = -c; ed[tot] = 0;
}
bool bfs() {
    rep (i, 1, n) v[i] = 0, d[i] = -inf;
    queue<int> q; q.push(s); v[s] = 1; d[s] = 0;
    incf[s] = inf; //增广路上各边的最小剩余容量
    while (!q.empty()) {
        int x = q.front(); q.pop(); v[x] = 0;
        for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
            if (!ed[i] || d[y] >= d[x] + co[i]) continue;
            d[y] = d[x] + co[i]; pre[y] = i;
            incf[y] = min(incf[x], ed[i]);
            if (!v[y]) q.push(y), v[y] = 1;
        }
    }
    return d[t] != -inf;
}
void update() {
    for (int x = t, i = pre[x]; x != s; i = pre[x = to[i ^ 1]])
        ed[i] -= incf[t], ed[i ^ 1] += incf[t];
    maxflow += incf[t]; ans += d[t] * incf[t];
}
int main() {
    IOS; while (cin >> n >> k) { while (bfs()) update(); cout << ans << '\n'; }
    return 0;
}

无源汇上下界网络流

int d[N], s, t, now[N], indeg[N], outdeg[N];
int h[N], ne[M], to[M], co[M], tot, ls[M >> 1];

void add(int u, int v, int c) { 
    ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c;
    ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = 0;
}

bool bfs() {
    memset(d, 0, sizeof d); memcpy(now, h, sizeof h);
    queue<int> q; q.push(s); d[s] = 1;
    while (!q.empty()) {
        int x = q.front(); q.pop();
        for (int i = h[x], y =  to[i]; i; y = to[i = ne[i]])
            if (!d[y] &&  co[i]) {
                d[y] = d[x] + 1; q.push(y);
                if (y == t) return 1;
            }
    }
    return 0;
}

int dinic(int x, int flow) {
    if (x == t) return flow;
    int res = flow, k;
    for (int i = now[x], y = to[i]; i && res; now[x] = i, y = to[i = ne[i]])
        if (d[y] == d[x] + 1 && co[i])
            if (!(k = dinic(y, min(res, co[i])))) d[y] = 0;
            else res -= k, co[i] -= k, co[i ^ 1] += k;
    return flow - res;
}

int main() {
    IOS; cin >> n >> m; tot = 1; s = 0, t = n + 1;
    rep (i, 1, m) {
        int u, v, x, y; cin >> u >> v >> x >> y;
        add(u, v, y - x); ls[i] = x;
        indeg[v] += x; outdeg[u] += x;
    }
    rep (i, 1, n)
        if (outdeg[i] > indeg[i]) add(i, t, outdeg[i] - indeg[i]);
        else if (outdeg[i] < indeg[i]) add(s, i, indeg[i] - outdeg[i]);
    while (bfs()) while (dinic(s, inf));
    for (int i = h[s]; i; i = ne[i])
        if (co[i]) return cout << "NO", 0;
    cout << "YES\n";
    rep (i, 1, m) cout << ls[i] + co[i << 1 | 1] << '\n';
    return 0;
}

有源汇上下界网络流

把给定的汇点向原点添加一条[\(0\), \(\infty\)]边, 并把给定的原汇点编程普通点, 变成无源汇

有源汇上下界最大流

先按有源汇上下界网络流使得流量平衡, 再把源汇点变为提题目给定的源汇点, 删除汇点间的w无穷边, 跑最大流即可

int main() {
    IOS; cin >> n >> m >> S >> T; tot = 1; s = 0, t = n + 1;
    rep (i, 1, m) {
        int u, v, x, y; cin >> u >> v >> x >> y;
        add(u, v, y - x); ls[i] = x;
        indeg[v] += x; outdeg[u] += x;
    }
    add(T, S, inf);
    rep (i, 1, n)
        if (outdeg[i] > indeg[i]) add(i, t, outdeg[i] - indeg[i]);
        else if (outdeg[i] < indeg[i]) add(s, i, indeg[i] - outdeg[i]);
    while (bfs()) while (dinic(s, inf));
    for (int i = h[s]; i; i = ne[i])
        if (co[i]) return cout << "No Solution", 0;
    int flow, mxflow = co[m + 1 << 1 | 1]; s = S, t = T;
    co[m + 1 << 1] = co[m + 1 << 1 | 1] = 0;
    while (bfs()) while (flow = dinic(s, inf)) mxflow += flow;
    cout << mxflow;
    return 0;
}

有源汇上下界最小流

同有源汇上下界最大流, 只不过变换之后, 是给定的源点变成汇点, 汇点变成源点
将流量退回去, 即给定的源汇点附加的无穷边反边的流量 减去 退回的流量
即最大流榨干参与网络, 最小流退回参与网络

int flow, mxflow = co[m + 1 << 1 | 1]; s = T, t = S;
co[m + 1 << 1] = co[m + 1 << 1 | 1] = 0;
while (bfs()) while (flow = dinic(s, inf)) mxflow -= flow;
cout << mxflow;

字符串

环的最大最小表示,

int get(char s[]) { //先复制一倍
    int i = 0, j = 1, k = 0, t;
    while (i < len && j < len && k < len) {
        t = s[(i + k) % len] - s[(j + k) % len];
        if (t == 0) ++k;
        else {
            if (t > 0) i += k + 1; //最大表示 t < 0
            else j += k + 1;
            if (i == j) ++j; k = 0;  
        }
    } return i > j ? j : i;
}

字符串哈希

ac自动机(trie树)

struct AC {
    static const int N = 1e5 + 5, M = 26, C = 'a'; //字符串总长度, 字符范围
    int trie[N][M], cnt[N], fail[N], q[N], tot;
    vector<VI> idx; //记录节点结尾的字符串id
    void init() {
        rep (i, 0, M - 1) trie[0][i] = 0;
        tot = 0; idx.resize(1, VI());
    }
    int newnode() {
        cnt[++tot] = 0; fail[tot] = 0; memset(trie[tot], 0, sizeof trie[tot]);
        return idx.pb(VI()), tot;
    }
    void insert(char* s, int id) {
        int p = 0;
        for (int i = 0, ch = s[i] - C; s[i]; p = trie[p][ch], ch = s[++i] - C)
            if (!trie[p][ch]) trie[p][ch] = newnode();
        ++cnt[p]; idx[p].pb(id);
    }
    void build() {
        int head = 0, tail = -1;
        rep (i, 0, M - 1) if (trie[0][i]) q[++tail] = trie[0][i];
        for (int p = q[head]; head <= tail; p = q[++head]) rep (i, 0, M - 1)
            if (trie[p][i])
                fail[trie[p][i]] = trie[fail[p]][i], q[++tail] = trie[p][i];
            else trie[p][i] = trie[fail[p]][i];
    }
    int query(char* s) {
        set<int> vis; int res = 0;
        for (int i = 0, p = trie[0][s[i] - C]; s[i]; p = trie[p][s[++i] - C])
            for (int tmp = p; tmp && !vis.count(tmp); tmp = fail[tmp])
                res += cnt[tmp], vis.insert(tmp);
        return res;
    }
} ac;

kmp

char t[LenT], s[LenS];
int lens, lent, cnt, f[LenT], extend[LenS];
void KMP() { //可根据f数组建立出sam的子集自动机(f[i]是endpos等价类子集)
    for (int i = 2, j = f[1] = 0; i <= lent; ++i) {
        while (j > 0 && t[i] != t[j + 1]) j = f[j];
        if (t[i] == t[j + 1]) ++j; f[i] = j;
    }
}
void ext_KMP() { //也可以直接 t = t + '#' + s; 直接kmp也行
    for (int i = 1, j = extend[1] = 0; i <= lens; ++i) {
        while (j > 0 && (j == lent || s[i] != t[j + 1])) j = f[j];
        extend[i] = s[i] == t[j + 1] ? ++j : j; cnt += (j == lent)
    }
}

Z函数(扩展KMP)

int lens, lent, f[N], extend[N];
char s[N], t[N];
void kmp(char* t, int lent) { //t从1开始
    int j = 0, k = 2;
    while (j + 2 <= lent && t[j + 1] == t[j + 2]) ++j;
    f[2] = j; f[1] = lent;
    for (int i = 3, p = k + f[k] - 1; i <= lent; ++i, p = k + f[k] - 1)
        if (i + f[i - k + 1] - 1 < p) f[i] = f[i - k + 1];
        else {
            j = max(0, p - i + 1);
            while (j + i <= lent && t[j + 1] == t[i + j]) ++j;
            f[i] = j; k = i;
        }
}
void ex_kmp(char *s, char *t, int lens, int lent) { //s, t下标都是从1开始
    int j = 0, k = 1;
    while (j + 1 <= min(lens, lent) && s[j + 1] == t[j + 1]) ++j;
    extend[1] = j;
    for (int i = 2, p = k + extend[k] - 1; i <= lens; ++i, p = k + extend[k] - 1)
        if (i + f[i - k + 1] - 1 < p) extend[i] = f[i - k + 1];
        else {
            j = max(0, p - i + 1);
            while (j + i <= lens && j + 1 <= lent && t[j + 1] == s[i + j]) ++j;
            extend[i] = j; k = i;
        }
}
int main() {
    IOS; cin >> s + 1 >> t + 1;
    lent = strlen(t + 1); lens = strlen(s + 1);
    kmp(t, lent); ex_kmp(s, t, lens, lent);
    rep (i, 1, lens) cout << extend[i] << ' ';
    return 0;
}

manachar

int pArr[N << 1];
char s[N], chaArr[N << 1];
int maxLcsplength(char *s) {
    int len = 0, R = -1, C = -1, maxN = 0; chaArr[len++] = '$', chaArr[len++] = '#';
    for (register int i = 0; s[i]; ++i) chaArr[len++] = s[i], chaArr[len++] = '#';
    chaArr[len] = '\0';
    for (register int i = 0; i < len; ++i) {
        pArr[i] = R > i ? min(R - i, pArr[(C << 1) - i]) : 1;
        while (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) ++pArr[i];
        if (i + pArr[i] > R) R = i + pArr[i], C = i; maxN = max(maxN, pArr[i]);
    } return maxN - 1;
}

序列自动机

struct SqAM {
    static const int N = 2e3 + 5, M = 26, C = 'a';
    struct Node { int fa, ne[26]; } tr[N << 1]; 
    int rt, tot, lst[M];
    int newNode() { return memset(tr[++tot].ne, 0, sizeof tr[0].ne), tot; }
    void init() { tot = 0; rep (i, 0, M - 1) lst[i] = 1; rt = newNode(); }
    void insert(int ch) {
        int p = lst[ch], cur = newNode(); tr[cur].fa = p;
        rep (i, 0, M - 1) for (int j = lst[i]; j && !tr[j].ne[ch]; j = tr[j].fa)
            tr[j].ne[ch] = cur;
        lst[ch] = cur;
    }
    void build(char* s) { for (int i = 0; s[i]; insert(s[i++] - C)); }
    bool find(char* s) {
        int p = 1;
        for (int i = 0; p && s[i]; p = tr[p].ne[s[i++] - C]);
        return p;
    }
};

后缀

前缀在整个串中出现的次数, sam处理完后遍历原串输出cnt即可
重复可重叠最长子串 自动机(max tr[tr[i].fa].len)
重复不可重叠最长子串 数组(二分, rk连续的一段且长度>=mid, 这段rk连续的sa位置最大最小值>mid)
重复k次的可重叠最长子串 自动机(cnt计数, 给fa节点打标记, 在cnt >= k && fa 节点取max len)
子串个数(相同字串不同位置算1/多个) 自动机
n个字串lca, sam上跑n次match, 取max ans[i]
两个后缀最长的lca,两个节点在parent树上的lca
重复次数最多的连续重复子串(某子串在某个长子串中不重叠出现次数最多, 求的是这个长子串),后缀数组

void solve() {
    Max = 0;
    for (int i = 1; i <= a.len; ++i)
        for (int j = 1; j + i <= a.len; j += i) {
            ans = a.rmq_query(j, j + i); k = j - (i - ans % i); ans = ans / i + 1;
            if (k >= 1 && a.rmq_query(k, k + i) >= i) ++ans;
            if (Max < ans) Max = ans, cnt = 0, q[cnt++] = i;
            else if (Max == ans && i != q[cnt - 1]) q[cnt++] = i;
        }
    //输出字典序最小的
    for (int i = 1; i <= a.len; ++i)
        for (int j = 0; j < cnt; ++j)
            if (a.rmq_query(a.sa[i], a.sa[i] + q[j]) >= q[j] * (Max - 1)) {
                a.s[a.sa[i] + q[j] * Max] = '\0';
                printf("%s\n", a.s + a.sa[i]); return;
            }
}

后缀自动机

struct SAM { //不管是不是多组数据都调用init
    static const int N = 5e5 + 5, M = 26, C = 'a';
    struct node { int fa, len, ne[M]; } tr[N << 1];
    int sz, las, len, c[N], rk[N << 1], cnt[N << 1];//(i~len)有cnt[i]个字母a[i]
    int sum[N << 1]; //排名为i的节点为头包含的字串数量
    int ans[N << 1], f[N << 1];
    void init() {
        rep (i, 1, sz)
            tr[i].len = tr[i].fa = c[i] = 0, memset(tr[i].ne, 0, sizeof tr[i].ne);
        sz = las = 1; len = 0;
    }
    void add(int ch) {
        int p = las, cur = las = ++sz;
        tr[cur].len = tr[p].len + 1; ++cnt[cur];
        for (; p && !tr[p].ne[ch]; p = tr[p].fa) tr[p].ne[ch] = cur;
        if (p == 0) { tr[cur].fa = 1; return; }
        int q = tr[p].ne[ch];
        if (tr[q].len == tr[p].len + 1) { tr[cur].fa = q; return; }
        int nq = ++sz; tr[nq] = tr[q]; tr[nq].len = tr[p].len + 1;
        for (; p && tr[p].ne[ch] == q; p = tr[p].fa) tr[p].ne[ch] = nq;
        tr[q].fa = tr[cur].fa = nq;
    }
    void build(char *s) {
        for (int& i = len; s[i]; ++i) add(s[i] - C);
    }
    void sort() {
        rep (i, 1, sz) c[i] = 0;
        rep (i, 1, sz) ++c[tr[i].len];
        rep (i, 1, len) c[i] += c[i - 1];
        rep (i, 1, sz) rk[c[tr[i].len]--] = i;
    }
    void getSizeLen(bool f) {
        per (i, sz, 2) //未考虑被压缩的字串(只出现过1次, 且不是原串的前缀)
            if (!f) cnt[rk[i]] = 1; //不同位置的相同字串算一个
            else cnt[tr[rk[i]].fa] += cnt[rk[i]]; //不同位置的相同字串算多个
        per (i, sz, 1) { //忽略tr[1]的大小
            sum[rk[i]] = i == 1 ? 0 : cnt[rk[i]];
            rep (j, 0, M - 1) if (tr[rk[i]].ne[j]) sum[rk[i]] += sum[tr[rk[i]].ne[j]];
        }
    }
    //t匹配s每个位置最大长度, 求多个串再此位置的最大值,就umin(ans[i],f[i]), 初始化ans=max
    int match(char *s) {
        int lenx = 0, p = 1, tmp = 0, mx = 0;
        memset(f, 0, sizeof f);
        for(int& i = lenx, ch = s[i] - C; s[i]; ch = s[++i] - C) {
            if (tr[p].ne[ch]) p = tr[p].ne[ch], ++tmp;
            else {
                while (!tr[p].ne[ch] && p) p = tr[p].fa;
                if(p == 0) p = 1, tmp = 0;
                else tmp = tr[p].len + 1, p = tr[p].ne[ch];
            } umax(f[p], tmp);
        }
        per (i, sz, 1) { p = tr[rk[i]].fa; umax(f[p], min(f[rk[i]], tr[p].len)); }
        rep (i, 2, sz) umin(ans[i], f[i]), umax(mx, ans[i]);
        return mx; 
    }
    void dfssub(int u) { //每个节点可以向下延申字串的数量,考虑被压缩的字串
        if (sum[u]) return; sum[u] = 1;
        for (int i = 0, v; i < M; ++i)
            if (v = tr[u].ne[i]) dfssub(v), sum[u] += sum[v];
    }
    //寻找字串中排名第x的字串,直接进来1, tr[1]空串排名0
    int kth(int k, char *s) {
        //memset(f, 0, sizoe f);
        if (sum[1] < k) return s[0] = '\0', 0;
        int cur = 1, len = 0;
        while (k) rep (i, 0, M - 1) if (tr[cur].ne[i]) {
            int v = tr[cur].ne[i];
            if (sum[v] >= k) { s[len++] = C + i; cur = v; k -= cnt[v]; break; }
            else k -= sum[v];
        } return s[len] = '\0', len;
    }
} sam;

后缀数组

struct SA {
    static const int N = 30000 + 9;
    char str[N]; //sa[i]表示排名i的后缀起始下标,rk[i]表示起始下标i后缀的排名
    int sa[N], rk[N], tp[N], tax[N], lcp[N], len, f[N][30], M, lg[N];
    inline void sort() {
        memset(tax, 0, (M + 1) * sizeof(int));
        rep (i, 1, len) ++tax[rk[i]];
        rep (i, 1, M) tax[i] += tax[i - 1];
        per (i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
    }
    void getH() {
        for (int i = 1, j, k = 0; i <= len; lcp[rk[i++]] = k) {
            if (k) --k; j = sa[rk[i] - 1];
            while (str[i + k] == str[j + k]) ++k;
        }
    }
    void SuffixSort() { //字符串下标从1开始
        M = 200; len = 1;
        for (int& i = len; str[i]; ++i) rk[i] = str[i], tp[i] = i;
        --len; sort();
        for (int w = 1, p = 0; p < len; w <<= 1, M = p) {
            p = 0;
            rep (i, 1, w) tp[++p] = len - w + i;
            rep (i, 1, len) if (sa[i] > w) tp[++p] = sa[i] - w;
            sort(); swap(tp, rk); rk[sa[1]] = p = 1;
            rep (i, 2, len)
                rk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]]
                    && tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
        } getH();
    }
    void rmq_init() {
        memset(f, 63, sizeof f); lg[1] = 0;
        rep (i, 2, len) lg[i] = lg[i + 1 >> 1] + 1;
        rep (i, 0, len - 1) f[i][0] = lcp[i + 1];
        for (int j = 1, mj = 2; mj <= len; ++j, mj <<= 1) rep (i, 1, len - mj)
                f[i][j] = min(f[i][j - 1], f[i + (mj >> 1)][j - 1]);
    }
    int rmq_query(int l, int r) {
        if (l < 1 || r > len) return 0;
        if (l == r) return len - l + 1;
        l = rk[l], r = rk[r];
        if (l > r) swap(l, r);
        return min(f[l][lg[r - l] - 1], f[r - k][lg[r - l] - 1]);
    }
} sa;

树上SA

struct SA { //树上后缀数组要用倍增, 叶子节点到根节点是一个字符串, 跟一般不同
    static const int N = 5e5 + 9;
    char str[N]; //sa[i]表示排名i的后缀起始下标,rk[i]表示起始下标i后缀的排名
    int sa[N], rk[N], tp[N], tax[N], len;
    int rk2[N], rkk[N]; //树上sa新增
    inline void sort(int* sa, int* rk, int* tp, int M) { //要排两次序
        memset(tax, 0, (M + 1) * sizeof(int));
        rep(i, 1, len) ++tax[rk[i]];
        rep(i, 1, M) tax[i] += tax[i - 1];
        per(i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
    }
    void SuffixSort() { //俩个串相同比较父亲串,再比较这俩串的节点
        int p; len = 1;
        for (int& i = len; str[i]; ++i) rk2[i] = str[i] - 'a' + 1, tp[i] = i;
        --len; sort(sa, rk2, tp, 30); rk[sa[1]] = rkk[sa[1]] = p = 1;
        rep(i, 2, len) {
            rk[sa[i]] = rk2[sa[i - 1]] == rk2[sa[i]] ? p : ++p;
            rkk[sa[i]] = i;
        }
        for (int w = 1, t = 0; w < len; w <<= 1, ++t) {
            rep(i, 1, len) rk2[i] = rkk[ST.f[i][t]];
            sort(tp, rk2, sa, len); sort(sa, rk, tp, p);
            swap(rk, tp); rk[sa[1]] = rkk[sa[1]] = p = 1;
            rep(i, 2, len) {
                rk[sa[i]] = tp[sa[i - 1]] == tp[sa[i]]
                    && tp[ST.f[sa[i - 1]][t]] == tp[ST.f[sa[i]][t]] ? p : ++p;
                rkk[sa[i]] = i;
            }
        }
        rep(i, 1, len) rk[i] = rkk[i];
    }
} sa;

广义后缀自动机

离线

struct EXSAM { //trie一样的空间 N * M, 再建自动机为 N * M << 1
    static const int N = 1e5 + 5, M = 26, C = 'a', NUM = 15;//N字串总长度,NUM字符串个数
    struct Node { int len, fa, ne[M]; } tr[N << 1]; //根据情况增大N,把c数组删了
    int tot, mxlen, curString, cnt[N << 1][NUM], tax[N << 1], rk[N << 1];
    int newNode() { return memset(tr[++tot].ne, 0, sizeof tr[0].ne), tot; }
    void init() { 
        memset(tr[0].ne, 0, sizeof tr[0].ne); 
        tot = mxlen = 0; tr[0].fa = -1;
    } //离线并且要对多个字串公共操作, 很难将数组cnt开好,只给你总长不给NUM很难受,在线好
    int insertSAM(int las, int ch) {
        int cur = tr[las].ne[ch], p = tr[las].fa;
        tr[cur].len = tr[las].len + 1;
        for (; p != -1 && !tr[p].ne[ch]; p = tr[p].fa) tr[p].ne[ch] = cur;
        if (p == -1) return tr[cur].fa = 0, cur;
        int q = tr[p].ne[ch];
        if (tr[p].len + 1 == tr[q].len) return tr[cur].fa = q, cur;
        int nq = ++tot; tr[nq].len = tr[p].len + 1; tr[nq].fa = tr[q].fa;
        rep (i, 0, M - 1) tr[nq].ne[i] = tr[tr[q].ne[i]].len ? tr[q].ne[i] : 0;
        for (; p != -1 && tr[p].ne[ch] == q; p = tr[p].fa) tr[p].ne[ch] = nq;
        return tr[cur].fa = tr[q].fa = nq, cur;
    }
    int insertTrie(int cur, int ch) {
        if (!tr[cur].ne[ch]) tr[cur].ne[ch] = newNode();
        ++cnt[tr[cur].ne[ch]][curString];
        return tr[cur].ne[ch];
    }
    void insert(const string& s) {
        for (int i = 0, p = 0; i < s.size(); p = insertTrie(p, s[i++] - C));
        umax(mxlen, s.size()); ++curString; //curString下标从0开始
    }
    void insert(const char *s) {
        int len = 0;
        for (int& i = len, p = 0; s[i]; p = insertTrie(p, s[i++] - C));
        umax(mxlen, len - 1); ++curString; //curString下标从0开始
    }
    void build() {
        queue<pair<int, int>> q;
        rep (i, 0, M - 1) if (tr[0].ne[i]) q.push({ i, 0 });
        while (!q.empty()) {
            PII it = q.front(); q.pop();
            int las = insertSAM(it.se, it.fi);
            rep (i, 0, M - 1) if (tr[las].ne[i]) q.push({ i, las });
        }
    }
    void sort() {
        rep (i, 1, tot) tax[i] = 0;
        rep (i, 1, tot) ++tax[tr[i].len];
        rep (i, 2, mxlen) tax[i] += tax[i - 1];
        rep (i, 1, tot) rk[tax[tr[i].len]--] = i;
    }
    void getSizeLen() {
        per(i, tot, 1)rep(j, 0, curString - 1)cnt[tr[rk[i]].fa][j] += cnt[rk[i]][j];
    }
    int mxComlca() { //最长公共子串
        int ans = 0;
        for (int i = 0, flag = 1; i <= tot; ++i) {
            rep (j, 0, curString - 1) if (!cnt[i][j]) { flag = 0; break; }
            if (flag) umax(ans, tr[i].len);
        }
        return ans;
    }
    ll difsub() { //不同位置相同字串算1个
        ll ans = 0;
        rep (i, 1, tot) ans += tr[i].len - tr[tr[i].fa].len;
        return ans;
    }
    int tag[N << 1], cnt[N];//cnt记录每个节点经过多少个字串
    void dfs(int p, int id) {
        for (; p != -1 && tag[p] != id; p = tr[p].fa) tag[p] = id, ++cnt[p];
    }
    void cntLen(char *s, int* len, int n) {
        rep (i, 1, n)
           for(int j=len[i-1],p=tr[0].ne[s[j]-C];j<len[i];p=tr[p].ne[s[++j]-C])dfs(p,i);
    } //s存n个字串,len第i个字串结尾的位置
} exSam;

回文自动机

struct PAM { //当len[i] > 0才是真实串(len[0]=0,len[1]=-1)
    static const int N = 260817, M = 26, C = 'a';
    struct Node { int ne[M], len, fail, cnt; } tr[N];
    int sz, tot, last;
    char s[N];
    int newNode(int l) {
        memset(tr[++sz].ne, 0, sizeof(tr[0].ne)); tr[sz].len = l;
        return tr[sz].fail = tr[sz].cnt = 0, sz;
    }
    void init() {
        sz = -1; last = 0; s[tot = 0] = '$';
        newNode(0); newNode(-1); tr[0].fail = 1;
    }
    int getfail(int x) {
        while (s[tot - tr[x].len - 1] != s[tot]) x = tr[x].fail;
        return x;
    }
    void insert(char c) {
        s[++tot] = c; int now = getfail(last), ch = c - C;
        if (!tr[now].ne[ch]) {
            int x = newNode(tr[now].len + 2);
            tr[x].fail = tr[getfail(tr[now].fail)].ne[ch];
            tr[now].ne[ch] = x;
        }
        ++tr[last = tr[now].ne[ch]].cnt;
    }
    void build(char *s) {
        for (int i = 0; s[i]; ++i) insert(s[i]);
        per (i, sz, 0) tr[tr[i].fail].cnt += tr[i].cnt;
    }
    ll solve() {
        ll ans = 0;
        rep (i, 1, sz) umax(ans, (ll)tr[i].len * tr[i].cnt);
        return ans;
    }
} pam;

Dancing linke

行为决策, 列为影响

数独

先考虑决策是什么。

在这一题中,每一个决策可以用形如\((r,c,w)\)的有序三元组表示。

注意到“宫”并不是决策的参数,因为它可以被每个确定的\((r,c)\)表示

因此有\(9 \times 9 \times 9 = 729\)行。

再考虑状态是什么。

我们思考一下\((r,c,w)\)这个决将会造成什么影响。记\((r,c)\)所在的宫为\(b\)

  1. \(r\)行用了一个\(w\)(用\(9 \times 9 = 81\)列表示);
  2. \(c\)列用了一个\(w\)(用\(9 \times 9 = 81\)列表示);
  3. \(b\)宫用了一个 (用\(9 \times 9 = 81\)列表示);
  4. \((r,c)\)中填入了一个数(用\(9 \times 9 = 81\)列表示)。
    因此有\(81 * 4 = 324\)列,共\(729 \times 4 = 2916\)\(1\)

至此,我们成功地将\(9 \times 9\)的数独问题转化成了一个 有\(729\)行,\(324\)列,共\(2916\)\(1\)的精确覆盖问题。

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

struct DLX {
    static const int N = 1e5 + 5;
#define IT(i, A, x) for(int i=A[x];i^x;i=A[i])
    int n, m, tot, first[N], siz[N], stk[N], ans;
    int L[N], R[N], U[N], D[N], col[N], row[N];
    void build(const int &r, const int &c) {
        for (int i = 0; i <= c; ++i) L[i] = i - 1, R[i] = i + 1, U[i] = D[i] = i;
        n = r, m = c; L[0] = c, R[c] = 0, tot = c;
        memset(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz));
    }
    void insert(const int &r, const int &c) {
        col[++tot] = c, row[tot] = r, ++siz[c];
        D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot;
        if (!first[r]) first[r] = L[tot] = R[tot] = tot;
        else {
            R[tot] = R[first[r]], L[R[first[r]]] = tot;
            L[tot] = first[r], R[first[r]] = tot;
        }
    }
    void remove(const int &c) {
        L[R[c]] = L[c], R[L[c]] = R[c];
        IT(i, D, c) IT(j, R, i) U[D[j]] = U[j], D[U[j]] = D[j], --siz[col[j]];
    }
    void recover(const int &c) {
        IT(i, U, c) IT(j, L, i) U[D[j]] = D[U[j]] = j, ++siz[col[j]];
        L[R[c]] = R[L[c]] = c;
    }
    bool dance(int dep) {
        if (!R[0]) return ans = dep, 1;
        int c = R[0];
        IT(i, R, 0) if (siz[i] < siz[c]) c = i;
        remove(c);
        IT(i, D, c) {
            stk[dep] = row[i];
            IT(j, R, i) remove(col[j]);
            if (dance(dep + 1)) return 1;
            IT(j, L, i) recover(col[j]);
        }
        recover(c);
        return 0;
    }
#undef IT
} dlx;

int a[10][10];

void insert(int r, int c, int n) {
    int g = (r - 1) / 3 * 3 + (c - 1) / 3 + 1;
    int id = (r - 1) * 81 + (c - 1) * 9 + n;
    dlx.insert(id, (r - 1) * 9 + n);
    dlx.insert(id, 81 + (c - 1) * 9 + n);
    dlx.insert(id, 162 + (g - 1) * 9 + n);
    dlx.insert(id, 243 + (r - 1) * 9 + c);
}

int main() {
    string s;
    while (cin >> s, s != "end") {
        dlx.build(729, 324);
        for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) {
            a[i][j] = s[(i - 1) * 9 + j - 1] == '.' ? 0 : s[(i - 1) * 9 + j - 1] ^ '0';
            for (int v = 1; v <= 9; ++v) if (!a[i][j] || a[i][j] == v) insert(i, j, v);
        }
        dlx.dance(0);
        for (int i = 0; i < dlx.ans; ++i)
            a[(dlx.stk[i] - 1) / 81 + 1][(dlx.stk[i] - 1) / 9 % 9 + 1] = (dlx.stk[i] - 1) % 9 + 1;
        for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) cout << a[i][j]; cout << '\n';
    }
  return 0;
}

靶形数独

这一题与数独的模型构建 一模一样,主要区别在于答案的更新。

这一题可以开一个权值数组,每次找到一组数独的解时,

每个位置上的数乘上对应的权值计入答案即可。

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

struct DLX {
    static const int N = 1e5 + 5;
#define IT(i, A, x) for(int i=A[x];i^x;i=A[i])
    int n, m, tot, first[N], siz[N], stk[N], ans;
    int L[N], R[N], U[N], D[N], col[N], row[N], mx, w[N];
    void build(const int &r, const int &c) {
        for (int i = 0; i <= c; ++i) L[i] = i - 1, R[i] = i + 1, U[i] = D[i] = i;
        n = r, m = c; L[0] = c, R[c] = 0, tot = c;
        memset(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz));
    }
    void insert(const int &r, const int &c, const int &W) {
        col[++tot] = c, row[tot] = r, w[tot] = W, ++siz[c];
        D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot;
        if (!first[r]) first[r] = L[tot] = R[tot] = tot;
        else {
            R[tot] = R[first[r]], L[R[first[r]]] = tot;
            L[tot] = first[r], R[first[r]] = tot;
        }
    }
    void remove(const int &c) {
        L[R[c]] = L[c], R[L[c]] = R[c];
        IT(i, D, c) IT(j, R, i) U[D[j]] = U[j], D[U[j]] = D[j], --siz[col[j]];
    }
    void recover(const int &c) {
        IT(i, U, c) IT(j, L, i) U[D[j]] = D[U[j]] = j, ++siz[col[j]];
        L[R[c]] = R[L[c]] = c;
    }
    bool dance(int dep, int cur) {
        if (!R[0]) return mx = max(cur, mx), 1;
        int c = R[0];
        IT(i, R, 0) if (siz[i] < siz[c]) c = i;
        remove(c);
        IT(i, D, c) {
            stk[dep] = row[i];
            IT(j, R, i) remove(col[j]);
            dance(dep + 1, cur + w[i]);
            IT(j, L, i) recover(col[j]);
        }
        recover(c);
        return 0;
    }
#undef IT
} dlx;

int a[10][10];

void insert(int r, int c, int n, int w) {
    int g = (r - 1) / 3 * 3 + (c - 1) / 3 + 1;
    int id = (r - 1) * 81 + (c - 1) * 9 + n;
    dlx.insert(id, (r - 1) * 9 + n, w);
    dlx.insert(id, 81 + (c - 1) * 9 + n, w);
    dlx.insert(id, 162 + (g - 1) * 9 + n, w);
    dlx.insert(id, 243 + (r - 1) * 9 + c, w);
}

int main() {
    dlx.build(729, 324);
    for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) {
        cin >> a[i][j]; int d = max(abs(i - 5), abs(j - 5));
        for (int v = 1; v <= 9; ++v) if (!a[i][j] || a[i][j] == v) insert(i, j, v, (10 - d) * v);
    }
    dlx.dance(0, 0); cout << (dlx.mx ? dlx.mx : -1);
    return 0;
}
posted @ 2021-10-24 14:51  洛绫璃  阅读(343)  评论(2编辑  收藏  举报