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


矩陣算法

掃描線線段樹

單調棧

掃描線

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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM