OI 基礎模板


\(\text{Update:}\)

2021.9.29
新增 次短路
歷史更新記錄

2021.9.25
新增 割點
2021.9.24 新增 哈希表
2021.9.17 STL 新增 vector
2021.9.16 新增 矩陣加速(數列) 更新了 進階 dp
2021.9.15 STL 新增 list, multimap, bieset 等
2021.9.10 更新 LCA 為 不含 lg 數組
2021.9.8 更新 FHQ-Treap 為能看的指針代碼 更新 線段樹 為效率高的指針代碼
2021.9.1 重寫了 tarjan 並更改模板題為 縮點 新增 Fast-Output
2021.8.29 線性求逆元和線性求歐拉函數
2021.8.20 更新 ST 表 和 Kruskal 為陽間代碼
2021.7.24 更新快速冪為zrt的快速冪
2021.7.22 新增 Splay
2021.7.7 新增樹鏈剖分
2021.7.4 更新線段樹區間為左閉右閉 新增可持久化數組、可持久化線段樹 將“常用 STL 初步”改名為“STL” 新增 STL:rope
2021.7.3 新增 Tarjan 輸出強連通分量
2021.7.2 新增圖的 BFS 與 DFS 參考代碼
2021.6.30 新增擴展 KMP 算法
2021.6.29 新增 KMP 和 LCA 新增 FHQ-Treap 新增 AC自動機
2021.6.28 新增 std::string 新增 Tarjan
2021.6.15 新增 Treap 和 左偏樹
2021.6.14 新增 Kruskal 最小生成樹
2021.5.30 單源最短路徑新增 vector 存圖的
2021.5.16 動態規划新增 LCIS (最長公共上升子序列)
2021.5.14 更新了區間dp的模板
2021.4.29 新增了珂朵莉樹 快速冪的代碼鍋了,現已修復 格式化了全部代碼!!!
2021.4.28 添加了單調隊列,包含 “定長” 和 “不定長”。
2021.4.23 求 gcd 的部分鍋了,重新寫了下。
2021.4.20 新增線段樹的區間加乘,區間修改。 重構了線段樹的代碼。 更新了部分標題名稱。

考試必備

Fast I/O

inline ll read()
{
    int x = 0, f = 0;
    char ch = getchar();
    while (!isdigit(ch))
        f |= (ch == '-'), ch = getchar();
    while (isdigit(ch))
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}

void write(ll x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + 48);
}

對拍

卡常小技巧

① 位運算乘除

$ x=x\times2^n 可以轉化成 x<<n $

$ x=x÷2^n 可以轉化成 x>>n $

② for 卡常

for (register int i(1); i <= n; ++i)

③ 短函數前加 inline 卡常

④ 判斷奇偶

if(a % 2 == 0)  可以轉化成  if((a & 1) == 0)

⑤ 取模用 &

x = 667 % 4;    可以轉化成  x = 667 & (4-1);
x = 667 % 32;   可以轉化成  x = 667 & (32-1);

⑥ 正負轉換用位運算

i = -1;       可以轉換成  i = ~i + 1;   或   i = (i ^ -1) + 1; 

⑦ 取絕對值

k = abs(x);   可以轉換成  k = (x ^ (x >> 31))-(x >> 31);  

數據結構

ST表

靜態查詢區間最值。

ll f[100001][20], lg[100001];
ll n, m, a[100001];

void pre()
{
    lg[0] = -1;
    for (int i = 1; i <= n; ++i)
        lg[i] = lg[i >> 1] + 1;
}

void ST_make()
{
    for (int i = 1; i <= n; ++i)
        f[i][0] = a[i];
    for (int j = 1; j < 22; ++j)
        for (int i = 1; i + (1 << j) - 1 <= n; ++i)
            f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

ll ST_check(ll l, ll r)
{
    ll k = lg[r - l + 1];
    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

int main()
{
    n = read(), m = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    pre();
    ST_make();
    for (int i = 1; i <= m; ++i)
    {
        ll l = read(), r = read();
        printf("%lld\n", ST_check(l, r));
    }
    return 0;
}

哈希表

不斷插入元素,詢問某元素出現次數。

struct node{
    int nxt, key, cnt;
} t[N];

ll head[mod + 20], tot;

void add(ll x)
{
    for(int i = head[x % mod]; i; i = t[i].nxt) // Already Have
    {
        if(t[i].key == x)  
        {
            ++t[i].cnt;
            return;
        }
    }
    t[++tot].key = x;
    t[tot].cnt = 1;
    t[tot].nxt = head[x % mod];
    head[x % mod] = tot;
}

ll check(ll x)
{
    for(int i = head[x % mod]; i; i = t[i].nxt)
        if(t[i].key == x) return t[i].cnt;
    return 0;
}

單調隊列

例題1

有一個長為 \(n\) 的序列 \(a\),以及一個大小為 \(k\) 的窗口。現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動后窗口中的最大值和最小值。

ll n, k, cnt = 0;
ll ans[2][1000005];

struct node
{
    ll sum, id;
};

deque<node> maxq;
deque<node> minq;

int main()
{
    n = read();
    k = read();
    node t;
    for (int i = 1; i <= n; ++i)
    {
        ll x = read();
        t.id = i;
        t.sum = x;
        while (!minq.empty() && x <= minq.back().sum)
            minq.pop_back();
        while (!maxq.empty() && x >= maxq.back().sum)
            maxq.pop_back();
        minq.push_back(t);
        maxq.push_back(t);
        while (i - k >= minq.front().id)
            minq.pop_front();
        while (i - k >= maxq.front().id)
            maxq.pop_front();
        if (i >= k)
        {
            ans[0][++cnt] = minq.front().sum;
            ans[1][cnt] = maxq.front().sum;
        }
    }
    for (int i = 1; i <= n - k + 1; ++i)
        printf("%lld ", ans[0][i]);
    puts("");
    for (int i = 1; i <= n - k + 1; ++i)
        printf("%lld ", ans[1][i]);
}

例題2

最大不定長子段和問題。

在一段長為 \(n\) 的數列中,找出一個長度 \(≤m\) 的子段,使得它的和是最大的。子段長度不能為0。

const ll maxn = 5000005;
#define INF 9223372036854775800

ll sum[maxn], q[maxn];
ll n, m;

int main()
{
    n = read();
    m = read();
    for (int i = 1; i <= n; ++i)
    {
        ll x = read();
        sum[i] = sum[i - 1] + x;
    }

    ll h = 1, t = 1, ans = -INF;
    q[1] = 0;
    for (int i = 1; i <= n; ++i)
    {
        while (h <= t && q[h] < i - m)
            h++;
        ans = max(ans, sum[i] - sum[q[h]]);
        while (h <= t && sum[i] <= sum[q[t]])
            t--;
        q[++t] = i;
    }
    printf("%lld\n", ans);
    Edison Ba;
}

樹狀數組

支持單點修改,區間查詢。

ll lowbit(ll x)
{
    return x & (-x);
}

ll c[500002], n, m;

void add(ll x, ll y) //單點修改
{
    for (; x <= n; x += lowbit(x))
        c[x] += y;
}

ll sum(ll x) //前綴和
{
    ll ans = 0;
    for (; x; x -= lowbit(x))
        ans += c[x];
    return ans;
}

ll ask(ll l, ll r) //區間查詢
{
    return sum(r) - sum(l - 1);
}

int main()
{
    n = read();
    m = read();
    for (int i = 1; i <= n; ++i) //初始化
    {
        ll x = read();
        add(i, x);
    }
    for (int i = 1; i <= m; ++i)
    {
        ll opt = read();
        if (opt == 1) //單點修改
        {
            ll x = read(), k = read();
            add(x, k);
        }
        else if (opt == 2) //區間查詢
        {
            ll x, y;
            x = read();
            y = read();
            ll ans = ask(x, y);
            printf("%lld\n", ans);
        }
    }
    return 0;
}

線段樹

P3373 【模板】線段樹 2

注:本線段樹使用鏈表形式(指針),每個結點都是左閉右閉區間。

操作為加或乘,最后答案模 \(mod\)

打兩個標記,分別為 addmul

ll n, m, mod;

struct node{
    ll L, R, add, mul, sum;
    node *lc, *rc;
    void makeadd(ll k)
    {
        sum = (sum + k * (R - L + 1)) % mod;
        add = (add + k) % mod;
    }
    void makemul(ll k)
    {
        sum = (sum * k) % mod;
        mul = (mul * k) % mod;
        add = (add * k) % mod;
    }
    void pushup()
    {
        sum = (lc->sum + rc->sum) % mod;
    }
    void pushdown()
    {
        if(mul != 1)
        {
            lc->makemul(mul);
            rc->makemul(mul);
            mul = 1;
        }
        if(add)
        {
            lc->makeadd(add);
            rc->makeadd(add);
            add = 0;
        }
    }
};

const ll N = 1e5 + 4;
ll a[N];

void Build(node *now, ll l, ll r)
{
    now->L = l;
    now->R = r;
    now->add = 0;
    now->mul = 1;
    now->sum = 0;
    if(l < r)
    {
        ll mid = (l + r) >> 1;
        now->lc = new node;
        now->rc = new node;
        Build(now->lc, l, mid);
        Build(now->rc, mid + 1, r);
        now->pushup();
    }
    else
    {
        now->sum = a[l];
        now->lc = now->rc = NULL;
    }
}

ll check(node *now, ll l, ll r)
{
    if(l <= now->L and now->R <= r)
        return now->sum % mod;
    now->pushdown();
    ll mid = (now->L + now->R) >> 1;
    ll ans = 0;
    if(l <= mid)
        ans = (ans + check(now->lc, l, r)) % mod;
    if(mid < r)
        ans = (ans + check(now->rc, l , r)) % mod;
    return ans % mod;
}

void add(node *now, ll l, ll r, ll k)
{
    if(l <= now->L and now->R <= r)
        now->makeadd(k);
    else
    {
        now->pushdown();
        ll mid = (now->L + now->R) >> 1;
        if(l <= mid)
            add(now->lc, l, r, k);
        if(mid < r)
            add(now->rc, l, r, k);
        now->pushup();
    }
}

void mul(node *now, ll l, ll r, ll k)
{
    if(l <= now->L and now->R <= r)
        now->makemul(k);
    else
    {
        now->pushdown();
        ll mid = (now->L + now->R) >> 1;
        if(l <= mid)
            mul(now->lc, l, r, k);
        if(mid < r)
            mul(now->rc, l, r, k);
        now->pushup();
    }
}

int main()
{
    n = read(), m = read(), mod = read();
    for(int i = 1; i <= n; ++i)
        a[i] = read();
    node *root;
    root = new node;
    Build(root, 1, n);
    for(int i = 1; i <= m; ++i)
    {
        ll opt = read(), l = read(), r = read(), k;
        if(opt == 1)
        {
            k = read();
            mul(root, l, r, k);
        }
        if(opt == 2)
        {
            k = read();
            add(root, l ,r, k);
        }
        if(opt == 3)
        {
            printf("%lld\n", check(root, l, r) % mod);
        }
    }
    return 0;
}

樹鏈剖分

\(n\) 個點, \(m\) 個操作數, 根結點為 \(R\), 取模數為 \(mod\)

輸入一顆樹。

支持的操作:

  1. \(x\) 點的點權增加(或修改)\(y\)
  2. 將樹從 \(x\)\(y\) 結點最短路徑上所有節點的值都加上 \(z\)
  3. 詢問某個節點 \(x\)\(y\) 節點的路徑中所有點的點權和 (或maxn)。
  4. \(x\) 為根結點的子樹中所有點的點權都增加 \(y\)
  5. 求以 \(x\) 為根節點的子樹內所有節點值之和
const ll N = 3e5 + 4;
ll n, m, tot, R, mod;
ll head[N], fa[N], son[N], siz[N], top[N], dep[N], seg[N], rev[N], a[N], lim[N];

struct nodee
{
    ll to, nxt;
} t[2 * N];

void add(ll x, ll y)
{
    t[++tot].to = y;
    t[tot].nxt = head[x];
    head[x] = tot;
}

void dfs1(ll u, ll father)
{
    siz[u] = 1, fa[u] = father, dep[u] = dep[father] + 1;
    for (ll i = head[u]; i; i = t[i].nxt)
    {
        ll y = t[i].to;
        if (siz[y] == 0)
        {
            dfs1(y, u);
            siz[u] += siz[y];
            if (siz[y] > siz[son[u]])
            {
                son[u] = y;
            }
        }
    }
}

void dfs2(ll u)
{
    seg[u] = ++seg[0];
    rev[seg[0]] = u;
    if (son[u])
    {
        top[son[u]] = top[u];
        dfs2(son[u]);
    }
    for (ll i = head[u]; i; i = t[i].nxt)
    {
        ll y = t[i].to;
        if (y == son[u] || fa[u] == y)
        {
            continue;
        }
        dfs2(y);
    }
    lim[u] = seg[0];
}

struct node
{
    ll L, R, sum, tag, maxn;
    node *lc, *rc;
};

void pushup(node *now)
{
    now->sum = (now->lc->sum + now->rc->sum) % mod;
    now->maxn = max(now->lc->maxn, now->rc->maxn);
}

inline void maketag(node *now, ll w)
{
    now->tag = (now->tag + w) % mod;
    now->sum = (now->sum + (now->R - now->L + 1) * w) % mod;
    now->maxn = w;
}

inline void pushdown(node *now)
{
    if (now->tag == 0)
        return;
    maketag(now->lc, now->tag);
    maketag(now->rc, now->tag);
    now->tag = 0;
}

void build(node *now, ll l, ll r)
{
    now->L = l;
    now->R = r;
    now->tag = 0;
    if (l < r)
    {
        ll mid = (l + r) >> 1;
        now->lc = new node;
        now->rc = new node;
        build(now->lc, l, mid);
        build(now->rc, mid + 1, r);
        pushup(now);
    }
    else
    {
        now->maxn = a[rev[l]];
        now->sum = a[rev[l]];
        now->lc = now->rc = NULL;
    }
}

void change(node *now, ll l, ll r, ll w)
{
    if (l <= now->L and now->R <= r)
    {
        maketag(now, w);
    }
    else if (!((now->L > r) || (now->R < l)))
    {
        pushdown(now);
        change(now->lc, l, r, w);
        change(now->rc, l, r, w);
        pushup(now);
    }
}

ll check1(node *now, ll l, ll r)
{
    if (l <= now->L and now->R <= r)
        return now->sum;
    if ((now->L > r) || (now->R < l))
        return 0;
    pushdown(now);
    return (check1(now->lc, l, r) + check1(now->rc, l, r)) % mod;
}

ll check2(node *now, ll l, ll r)
{
    if (l <= now->L and now->R <= r)
        return now->maxn;
    if ((now->L > r) || (now->R < l))
        return -INF;
    pushdown(now);
    return max(check2(now->lc, l, r), check2(now->rc, l, r));
}

int main()
{
    n = read(), m = read();
    R = 1, mod = INF; // R為根結點序號

    for (ll i = 1; i <= n; i++)
        top[i] = i;

    for (ll i = 1; i <= n; i++)
        a[i] = read();

    for (ll i = 1; i <= n - 1; i++)
    {
        ll x = read(), y = read();
        add(x, y);
        add(y, x);
    }

    dfs1(R, 0);
    dfs2(R);

    node *root;
    root = new node;
    build(root, 1, n);

    for (int i = 1; i <= m; ++i)
    {
        ll opt = read(), x = read(), y, z;

        // 把 x 點的點權增加 y
        // 如果想要修改,更改上面的 maketag
        if (opt == 0) 
        {
            y = read(); 
            change(root, seg[x], seg[x], y);
        }

        // 表示將樹從 x 到 y 結點最短路徑上所有節點的值都加上 z。
        else if (opt == 1)
        {
            y = read(), z = read();
            while (top[x] != top[y])
            {
                if (dep[top[x]] < dep[top[y]])
                    swap(x, y);
                change(root, seg[top[x]], seg[x], z);
                x = fa[top[x]];
            }
            if(seg[x] > seg[y]) swap(x, y);
            change(root, seg[x], seg[y], z);
        }
        
        // 詢問某個節點 x 到 y 節點的路徑中所有點的點權和 (或maxn)
        else if (opt == 2)
        {
            y = read();
            ll sum = 0;
            // ll maxn = -2147483647;
            while (top[x] != top[y])
            {
                if (dep[top[x]] < dep[top[y]])
                    swap(x, y);
                sum = (sum + check1(root, seg[top[x]], seg[x])) % mod;
                // maxn = max(maxn, check2(root, seg[top[x]], seg[x]));
                x = fa[top[x]];
            }
            if (seg[x] > seg[y])
                swap(x, y);
            sum = (sum + check1(root, seg[x], seg[y])) % mod;
            // maxn = max(maxn, check2(root, seg[x], seg[y]));
            printf("%lld\n", sum);
        }

        // 把 x 為根結點的子樹中所有點的點權都增加 y
        else if (opt == 3) 
        {
            y = read();
            change(root, seg[x], lim[x], y);
        }

        // 求以 x 為根節點的子樹內所有節點值之和
        else if (opt == 4)
        {
            ll ans = check1(root, seg[x], lim[x]) % mod;
            printf("%lld\n", ans);
        }
    }
    return 0;
}

平衡樹

FHQ-Treap

普通平衡樹

您需要寫一種數據結構,來維護一些數,其中需要提供以下操作:

  1. 插入 \(x\)
  2. 刪除 \(x\) 數(若有多個相同的數,因只刪除一個)
  3. 查詢 \(x\) 數的排名(排名定義為比當前數小的數的個數 \(+1\) )
  4. 查詢排名為 \(x\) 的數
  5. \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)
  6. \(x\) 的后繼(后繼定義為大於 \(x\),且最小的數)

第一行為 \(n\),表示操作的個數,下面 \(n\) 行每行有兩個數 \(\text{opt}\)\(x\)\(\text{opt}\) 表示操作的序號\(( 1≤opt≤6 )\)

對於操作 \(3,4,5,6\) 每行輸出一個數,表示對應答案


ll n;

struct node{
    ll size, w, ran;
    node *lc, *rc;
    node(int x)
    {
        size = 1, w = x, ran = rand();
        lc = rc = NULL;
    }
} *root = NULL;

void pushup(node *now)
{
    now->size = 1;
    if(now->lc != NULL) now->size += now->lc->size;
    if(now->rc != NULL) now->size += now->rc->size;
}

void split(node *now , ll k, node *&x, node *&y)
{
    if(now == NULL)
    {
        x = y = NULL;
        return ;
    }
    if(k < now->w)
        y = now, split(now->lc, k, x, now->lc);
    else 
        x = now, split(now->rc, k ,now->rc, y);
    pushup(now);
}

node *merge(node *x, node *y)
{
    if(x == NULL || y == NULL)
        return x == NULL ? y : x;
    if(x->ran < y->ran)
    {
        x->rc = merge(x->rc, y);
        pushup(x);
        return x;
    }
    else
    {
        y->lc = merge(x, y->lc);
        pushup(y);
        return y;
    }
}

void Insert(ll k)
{
    node *x, *y, *now = new node(k);
    split(root, k, x, y);
    root = merge(merge(x, now), y);
}

void Del(ll k)
{
    node *x, *y, *z;
    split(root, k, x, z);
    split(x, k - 1, x, y);
    y = merge(y->lc, y->rc);
    root = merge(merge(x, y), z);
}

ll Ran(ll k)
{
    ll ans = 0;
    node *x, *y;
    split(root, k - 1, x, y);
    ans = (x == NULL ? 0 : x->size) + 1;
    root = merge(x, y);
    return ans;
}

ll Kth(ll k)
{
    node *now = root;
    ll lsize;
    while(1)
    {
        lsize = (now->lc == NULL ? 0 : now->lc->size);
        if(lsize + 1 == k) break;
        else if(k <= lsize) now = now->lc;
        else
        {
            k -= lsize + 1;
            now = now->rc;
        }
    }
    return now->w;
}

int main()
{
    n = read();
    for(int i = 1; i <= n; ++i)
    {
        ll opt = read(), x = read();
        if(opt == 1) Insert(x);
        if(opt == 2) Del(x);
        if(opt == 3) printf("%lld\n", Ran(x));
        if(opt == 4) printf("%lld\n", Kth(x));
        if(opt == 5) printf("%lld\n", Kth(Ran(x) - 1));
        if(opt == 6) printf("%lld\n", Kth(Ran(x + 1)));
    }
    return 0;   
}
文藝平衡樹

const ll N = 1e6 + 3;
ll n, m, a[N];

struct node
{
    ll size, w, ran, add;
    node *lc, *rc;
    node(int x)
    {
        size = 1, w = x, ran = rand(), add = 0;
        lc = rc = NULL;
    }
} *root = NULL;

void pushdown(node *now)
{
    swap(now->lc, now->rc);
    if (now->lc != NULL)
        now->lc->add ^= 1;
    if (now->rc != NULL)
        now->rc->add ^= 1;
    now->add = 0;
}

void pushup(node *now)
{
    now->size = 1;
    if (now->lc != NULL)
        now->size += now->lc->size;
    if (now->rc != NULL)
        now->size += now->rc->size;
}

void split(node *now, ll k, node *&x, node *&y)
{
    if (now == NULL)
    {
        x = y = NULL;
        return;
    }
    if (now->add)
        pushdown(now);
    ll ls = (now->lc == NULL) ? 1 : now->lc->size + 1;
    if (k < ls)
        y = now, split(now->lc, k, x, now->lc);
    else
        x = now, split(now->rc, k - ls, now->rc, y);
    pushup(now);
}

node *merge(node *x, node *y)
{
    if (x == NULL || y == NULL)
        return x == NULL ? y : x;
    if (x->ran < y->ran)
    {
        if (x->add)
            pushdown(x);
        x->rc = merge(x->rc, y);
        pushup(x);
        return x;
    }
    else
    {
        if(y->add) pushdown(y);
        y->lc = merge(x, y->lc);
        pushup(y);
        return y;
    }
}

node *Build(ll l, ll r)
{
    node *now;
    if (l == r)
        return now = new node(a[l]);
    ll mid = (l + r) >> 1;
    return merge(Build(l, mid), Build(mid + 1, r));
}

void Insert()
{
    for (int i = 1; i <= n; ++i)
        a[i] = i;
    root = Build(1, n);
}

void Reverse(ll l, ll r)
{
    node *x, *y, *z;
    split(root, r, x, z);
    split(x, l - 1, x, y);
    y->add ^= 1;
    root = merge(merge(x, y), z);
}

void Print(node *now)
{
    if(now->add) pushdown(now);
    if(now->lc) Print(now->lc);
    printf("%lld ", now->w);
    if(now->rc) Print(now->rc);
}

int main()
{
    n = read(), m = read();
    Insert();
    for (int i = 1; i <= m; ++i)
    {
        ll l = read(), r = read();
        Reverse(l, r);
    }
    Print(root);
    return 0;
}

Treap

const ll maxn = 1e5 + 10;
const ll inf = 1e14 + 10;
ll n, tot, root;

struct node
{
    ll l, r;
    ll v, rk;
    ll cnt, si
} s[maxn << 1];

inline ll New(ll w)
{
    s[++tot].v = w;
    s[tot].rk = rand();
    s[tot].cnt = s[tot].siz = 1;
    return tot;
}

inline void upd(ll p)
{
    s[p].siz = s[s[p].l].siz + s[s[p].r].siz + s[p].cnt;
}

inline void build()
{
    New(-inf), New(inf);
    root = 1;
    s[1].r = 2;
    upd(root);
    return;
}

inline void zig(ll &p)
{
    ll q = s[p].l;
    s[p].l = s[q].r, s[q].r = p;
    p = q;
    upd(s[p].r);
    upd(p);
}

inline void zag(ll &p)
{
    ll q = s[p].r;
    s[p].r = s[q].l, s[q].l = p;
    p = q;
    upd(s[p].l);
    upd(p);
}

inline void add(ll &p, ll x)
{
    if (p == 0)
    {
        p = New(x);
        return;
    }
    if (s[p].v == x)
    {
        s[p].cnt++;
        upd(p);
        return;
    }
    if (s[p].v < x)
    {
        add(s[p].r, x);
        if (s[p].rk < s[s[p].r].rk)
            zag(p);
    }
    if (s[p].v > x)
    {
        add(s[p].l, x);
        if (s[p].rk < s[s[p].l].rk)
            zig(p);
    }

    upd(p);
}

inline void delt(ll &p, ll x)
{
    if (p == 0)
        return;
    if (s[p].v == x)
    {
        if (s[p].cnt > 1)
        {
            s[p].cnt--;
            upd(p);
            return;
        }
        if (s[p].l || s[p].r)
        {
            if (s[p].r == 0 || s[s[p].l].rk > s[s[p].r].rk)
            {
                zig(p);
                delt(s[p].r, x);
            }
            else
            {
                zag(p);
                delt(s[p].l, x);
            }
            upd(p);
        }
        else
            p = 0;
        return;
    }
    if (s[p].v < x)
    {
        delt(s[p].r, x);
        upd(p);
        return;
    }
    if (s[p].v > x)
    {
        delt(s[p].l, x);
        upd(p);
        return;
    }
}

inline ll getrank(ll p, ll x)
{
    if (p == 0)
        return 0;
    if (s[p].v == x)
    {
        return s[s[p].l].siz + 1;
    }
    if (s[p].v > x)
    {
        return getrank(s[p].l, x);
    }
    else
    {
        return getrank(s[p].r, x) + s[s[p].l].siz + s[p].cnt;
    }
}

inline ll getval(ll p, ll x)
{
    if (p == 0)
        return inf;
    if (s[s[p].l].siz >= x)
    {
        return getval(s[p].l, x);
    }
    if (s[s[p].l].siz + s[p].cnt >= x)
    {
        return s[p].v;
    }
    else
    {
        return getval(s[p].r, x - s[s[p].l].siz - s[p].cnt);
    }
}

inline ll getpre(ll x)
{
    ll ans = 1;
    ll p = root;
    while (p)
    {
        if (x == s[p].v)
        {
            if (s[p].l)
            {
                p = s[p].l;
                while (s[p].r)
                    p = s[p].r;
                ans = p;
            }
            break;
        }
        if (s[p].v < x && s[p].v > s[ans].v)
            ans = p;
        if (x < s[p].v)
            p = s[p].l;
        else
            p = s[p].r;
    }
    return s[ans].v;
}

inline ll getnxt(ll x)
{
    ll ans = 2;
    ll p = root;
    while (p)
    {
        if (x == s[p].v)
        {
            if (s[p].r)
            {
                p = s[p].r;
                while (s[p].l)
                    p = s[p].l;
                ans = p;
            }
            break;
        }
        if (s[p].v > x && s[p].v < s[ans].v)
            ans = p;
        if (x < s[p].v)
            p = s[p].l;
        else
            p = s[p].r;
    }
    return s[ans].v;
}

int main()
{
    build();
    n = read();
    for (int i = 1; i <= n; i++)
    {
        ll op = read(), x = read();
        if (op == 1)
            add(root, x);
        if (op == 2)
            delt(root, x);
        if (op == 3)
            printf("%lld\n", getrank(root, x) - 1);
        if (op == 4)
            printf("%lld\n", getval(root, x + 1));
        if (op == 5)
            printf("%lld\n", getpre(x));
        if (op == 6)
            printf("%lld\n", getnxt(x));
    }
    return 0;
}

Splay

struct node
{
    ll cnt, val, size;
    node *son[2], *fa;
};

node *root, *null;

inline void C(node *&now)
{
    now->son[0] = now->son[1] = now->fa = null;
    now->val = now->cnt = now->size = 0;
}

inline bool getwhich(node *now)
{
    return now->fa->son[1] == now;
}

inline void rotate(node *now)
{
    node *fa = now->fa;
    node *gfa = fa->fa;
    ll idson = getwhich(now);
    gfa->son[getwhich(fa)] = now;
    now->fa = gfa;
    fa->son[idson] = now->son[idson ^ 1];
    now->son[idson ^ 1]->fa = fa;
    now->son[idson ^ 1] = fa;
    fa->fa = now;
    fa->size = fa->son[0]->size + fa->son[1]->size + fa->cnt;
    now->size = now->son[0]->size + now->son[1]->size + now->cnt;
}

inline void Splay(node *now, node *f)
{
    while (now->fa != f)
    {
        node *fa = now->fa;
        node *gfa = fa->fa;
        if (gfa != f)
            getwhich(now) ^ getwhich(fa) ? rotate(now) : rotate(fa);
        rotate(now);
    }
    if (f == null)
        root = now;
}

inline void insert(ll x)
{
    node *now = root, *fa = null;
    while (now != null and now->val != x)
    {
        fa = now;
        now = now->son[x > now->val];
    }
    if (now != null)
        ++now->cnt;
    else
    {
        node *p = new node;
        if (fa != null)
            fa->son[x > fa->val] = p;
        p->fa = fa;
        p->val = x;
        p->size = p->cnt = 1;
        p->son[0] = p->son[1] = null;
        Splay(p, null);
        return;
    }
    Splay(now, null);
}

void search(ll x)
{
    node *now = root;
    if (now == null)
        return;
    while (now->son[x > now->val] != null and x != now->val)
        now = now->son[x > now->val];
    Splay(now, null);
}

node *getpre(ll x)
{
    search(x);
    node *now = root->son[0];
    while (now->son[1] != null)
        now = now->son[1];
    return now;
}

node *getback(ll x)
{
    search(x);
    node *now = root->son[1];
    while (now->son[0] != null)
        now = now->son[0];
    return now;
}

ll getnum(ll x)
{
    node *now = root;
    while (1)
    {
        if (x <= now->son[0]->size)
            now = now->son[0];
        else
        {
            x -= (now->son[0]->size + now->cnt);
            if (x <= 0)
                return now->val;
            else
                now = now->son[1];
        }
    }
}

void _delete(ll x)
{
    node *pre = getpre(x), *back = getback(x);
    Splay(pre, null);
    Splay(back, pre);
    if (back->son[0]->cnt > 1)
    {
        --back->son[0]->cnt;
        Splay(back->son[0], null);
    }
    else
    {
        back->son[0] = null;
        C(back->son[0]);
    }
}

int main()
{
    null = new node;
    null->size = null->cnt = null->val = 0;
    null->son[0] = null->son[1] = null->fa = NULL;
    root = null;
    n = read();
    insert(-INF);
    insert(INF);
    for (int i = 1; i <= n; ++i)
    {
        ll opt = read(), x = read();
        if (opt == 1)
        {
            insert(x);
        }
        else if (opt == 2)
        {
            _delete(x);
        }
        else if (opt == 3)
        {
            search(x);
            printf("%lld\n", root->son[0]->size);
        }
        else if (opt == 4)
        {
            printf("%lld\n", getnum(x + 1));
        }
        else if (opt == 5)
        {
            insert(x);
            printf("%lld\n", getpre(x)->val);
            _delete(x);
        }
        else
        {
            insert(x);
            printf("%lld\n", getback(x)->val);
            _delete(x);
        }
    }
    return 0;
}

可持久化數組

P3919 【模板】可持久化線段樹 1(可持久化數組)

如題,你需要維護這樣的一個長度為 \(N\) 的數組,支持如下幾種操作:

  1. 在某個歷史版本上修改某一個位置上的值

  2. 訪問某個歷史版本上的某一位置的值

此外,每進行一次操作(對於操作 \(2\),即為生成一個完全一樣的版本,不作任何改動),就會生成一個新的版本。版本編號即為當前操作的編號(從 \(1\) 開始編號,版本 \(0\) 表示初始狀態數組)

  1. 對於操作1,格式為 \(v_i ~1 ~loc_i~ value_i\),即為在版本 \(v_i\) ​的基礎上,將 \(a_{{loc}_i}\) 修改為 \({value}_i\)

  2. 對於操作2,格式為 \(v_i \ 2 \ {loc}_i\)​,即訪問版本 \(v_i\) ​中的 \(a_{{loc}_i}\) ​​的值,生成一樣版本的對象應為 \(v_i\)

輸出包含若干行,依次為每個操作2的結果。

學會動態開點很重要。本代碼采用指針形式,點區間為左閉右閉。

const ll N = 1e6 + 3;
int n, m, tot, a[N];

struct node
{
    int L, R, w;
    node *lc, *rc;
};

struct node by[N * 21], *pool = by, *root[N];

node *New()
{
    return ++pool;
}

node *build(int l, int r)
{
    node *now = New();
    now->L = l;
    now->R = r;
    if (l < r)
    {
        ll mid = (now->L + now->R) >> 1;
        now->lc = build(l, mid);
        now->rc = build(mid + 1, r);
    }
    else
    {
        now->w = a[l];
        now->lc = now->rc = NULL;
    }
    return now;
}

inline bool out(node *&now, int l, int r)
{
    return (now->R < l) || (r < now->L);
}

void change(node *&pre, node *&now, int x, int w)
{
    *now = *pre;
    if (pre->L == x and pre->R == x)
        now->w = w;
    else
    {
        if (!out(pre->lc, x, x))
        {
            now->lc = New();
            change(pre->lc, now->lc, x, w);
        }
        else
        {
            now->rc = New();
            change(pre->rc, now->rc, x, w);
        }
    }
}

int check(node *now, int x)
{
    if (now->L == x && now->R == x)
        return now->w;
    if (!out(now->lc, x, x))
        return check(now->lc, x);
    else
        return check(now->rc, x);
}

int main()
{
    n = read(), m = read();
    for (int i = 1; i <= n; ++i)
    {
        a[i] = read();
    }
    root[0] = build(1, n);
    for (int i = 0; i < m; ++i)
    {
        ll v = read(), opt = read(), x = read(), k;
        if (opt == 1)
        {
            k = read();
            root[++tot] = New();
            change(root[v], root[tot], x, k);
        }
        else
        {
            ll ans = check(root[v], x);
            printf("%lld\n", ans);
            root[++tot] = New();
            root[tot] = root[v];
        }
    }
    return 0;
}

可持久化線段樹

P3834 【模板】可持久化線段樹 2(主席樹)

這是個非常經典的主席樹入門題——靜態區間第 \(k\) 小。

對於指定的閉區間 \([l, r]\) 查詢其區間內的第 \(k\) 小值.

代碼采用指針形式,區間為左閉右閉。

const ll M = 2e5 + 3;
int n, N, m, tot, a[M], b[M];

struct node
{
    int L, R, cnt;
    node *lc, *rc;
};

struct node by[M * 21], *pool = by, *root[M];

node *New()
{
    return ++pool;
}

void update(node *&now)
{
    now->cnt = now->lc->cnt + now->rc->cnt;
}

node *build(int l, int r)
{
    node *now = New();
    now->L = l;
    now->R = r;
    if (l < r)
    {
        int mid = (l + r) >> 1;
        now->lc = build(l, mid);
        now->rc = build(mid + 1, r);
        update(now);
    }
    else
    {
        now->cnt = 0;
        now->lc = now->rc = NULL;
    }
    return now;
}

inline bool out(node *&now, int l, int r)
{
    return (now->R < l) || (r < now->L);
}

void change(node *pre, node *now, int x)
{
    *now = *pre;
    if (pre->L == x and pre->R == x)
        now->cnt++;
    else
    {
        if (!out(pre->lc, x, x))
        {
            now->lc = New();
            change(pre->lc, now->lc, x);
            update(now);
        }
        else
        {
            now->rc = New();
            change(pre->rc, now->rc, x);
            update(now);
        }
    }
}

int check(node *&nowl, node *&nowr, int k)
{
    if (nowl->L == nowl->R)
        return nowl->L;
    int lcnt = nowr->lc->cnt - nowl->lc->cnt;
    if (lcnt >= k)
        return check(nowl->lc, nowr->lc, k);
    else
        return check(nowl->rc, nowr->rc, k - lcnt);
}

void Main()
{
    n = read();
    m = read();
    for (int i = 1; i <= n; ++i)
    {
        a[i] = read();
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    N = unique(b + 1, b + n + 1) - b - 1;
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(b + 1, b + N + 1, a[i]) - b;
    root[0] = build(1, N);
    for (int i = 1; i <= n; ++i)
    {
        root[++tot] = New();
        change(root[tot - 1], root[tot], a[i]);
    }
    for (int i = 1; i <= m; ++i)
    {
        int l, r, k;
        l = read(), r = read(), k = read();
        int ans = b[check(root[l - 1], root[r], k)];
        printf("%d\n", ans);
    }
}

左偏樹

一開始有 \(n\) 個小根堆,每個堆包含且僅包含一個數。接下來需要支持兩種操作:

  1. 1 x y:將第 \(x\) 個數和第 \(y\) 個數所在的小根堆合並(若第 \(x\) 或第 \(y\) 個數已經被刪除或第 \(x\) 和第 \(y\) 個數在用一個堆內,則無視此操作)。
  2. 2 x:輸出第 \(x\) 個數所在的堆最小數,並將這個最小數刪除(若有多個最小數,優先刪除先輸入的;若第 \(x\) 個數已經被刪除,則輸出 \(-1\) 並無視刪除操作)。

第一行包含兩個正整數 \(n,m\),分別表示一開始小根堆的個數和接下來操作的個數。

第二行包含 \(n\) 個正整數,其中第 \(i\) 個正整數表示第 \(i\) 個小根堆初始時包含且僅包含的數。

接下來 \(m\) 行每行 \(2\) 個或 \(3\) 個正整數,表示一條操作。

輸出包含若干行整數,分別依次對應每一個操作 \(2\) 所得的結果。

#define M 150010
#define swap my_swap
#define ls S[x].Son[0]
#define rs S[x].Son[1]

struct Tree
{
    ll dis, val, Son[2], rt;
} S[M];
ll N, T, A, B, C, i;

inline ll Merge(ll x, ll y);
ll my_swap(ll &x, ll &y) { x ^= y ^= x ^= y; }
inline ll Get(ll x) { return S[x].rt == x ? x : S[x].rt = Get(S[x].rt); }
inline void Pop(ll x) { S[x].val = -1, S[ls].rt = ls, S[rs].rt = rs, S[x].rt = Merge(ls, rs); }
inline ll Merge(ll x, ll y)
{
    if (!x || !y)
        return x + y;
    if (S[x].val > S[y].val || (S[x].val == S[y].val && x > y))
        swap(x, y);
    rs = Merge(rs, y);
    if (S[ls].dis < S[rs].dis)
        swap(ls, rs);
    S[ls].rt = S[rs].rt = S[x].rt = x, S[x].dis = S[rs].dis + 1;
    return x;
}
int main()
{
    N = read(), T = read();
    S[0].dis = -1;
    for (i = 1; i <= N; ++i)
        S[i].rt = i, S[i].val = read();
    for (i = 1; i <= T; ++i)
    {
        A = read(), B = read();
        if (A == 1)
        {
            C = read();
            if (S[B].val == -1 || S[C].val == -1)
                continue;
            ll f1 = Get(B), f2 = Get(C);
            if (f1 != f2)
                S[f1].rt = S[f2].rt = Merge(f1, f2);
        }
        else
        {
            if (S[B].val == -1)
                puts("-1");
            else
                printf("%lld\n", S[Get(B)].val), Pop(Get(B));
        }
    }
    return 0;
}

珂朵莉樹

神奇的暴力數據結構

例題

\(n\) 個數,\(m\) 次操作 \((n,m≤105)\)

操作:

  • 區間加
  • 區間賦值
  • 區間第k小
  • 求區間冪次和

數據隨機,時限 \(2s\)

關鍵操作:推平一段區間,使一整段區間內的東西變得一樣。保證數據隨機

#include <set>
#include <vector>
#define p 1000000007
#define IT set<node>::iterator

/* 初始化 */
struct node
{
    ll l, r;
    mutable ll w; //可變的
    node(ll L, ll R = -1, ll W = 0)
    {
        l = L;
        r = R;
        w = W;
    }
    bool operator<(const node &o) const
    {
        return l < o.l;
    }
};


ll n, m, seed, vmax, a[100005];
set<node> s;

ll rnd() /* 對於本題的數據生成器 */
{
    ll ans = seed;
    seed = (seed * 7 + 13) % p;
    return ans;
}

ll ksm(){}

IT split(ll pos) /* 分裂 */
{
    IT it = s.lower_bound(node(pos));
    if (it != s.end() && it->l == pos)
        return it;
    --it;
    ll L = it->l, R = it->r;
    ll W = it->w;
    s.erase(it);
    s.insert(node(L, pos - 1, W));
    return s.insert(node(pos, R, W)).first;
}

void assign(ll l, ll r, ll w = 0) /* 推平 */
{
    IT itl = split(l), itr = split(r + 1);
    s.erase(itl, itr);
    s.insert(node(l, r, w));
}

void add(ll l, ll r, ll w = 1) /* 暴力區間加 */
{
    IT itl = split(l), itr = split(r + 1);
    for (; itl != itr; ++itl)
        itl->w += w;
}

ll rnk(ll l, ll r, ll k) /* 暴力區間第 k 小 */
{
    vector<pair<ll, int>> vp;
    IT itl = split(l), itr = split(r + 1);
    vp.clear();
    for (; itl != itr; ++itl)
        vp.push_back(pair<ll, int>(itl->w, itl->r - itl->l + 1));
    std::sort(vp.begin(), vp.end());
    for (vector<pair<ll, int>>::iterator it = vp.begin(); it != vp.end(); ++it)
    {
        k -= it->second;
        if (k <= 0)
            return it->first;
    }
    return -1LL;
}

ll sum(ll l, ll r, ll ex, ll mod) /* 暴力求和 */
{
    IT itl = split(l), itr = split(r + 1);
    ll res = 0;
    for (; itl != itr; ++itl)
        res = (res + (ll)(itl->r - itl->l + 1) * ksm(itl->w, ex, mod)) % mod;
    return res;
}

int main()
{
    n = read();
    m = read();
    seed = read();
    vmax = read();
    for (int i = 1; i <= n; ++i)
    {
        a[i] = (rnd() % vmax) + 1;
        s.insert(node(i, i, a[i]));
    }
    s.insert(node(n + 1, n + 1, 0));

    for (int i = 1; i <= m; ++i)
    {
        ll op = 1LL * (rnd() % 4) + 1;
        ll l = 1LL * (rnd() % n) + 1;
        ll r = 1LL * (rnd() % n) + 1;
        if (l > r)
            swap(l, r);
        ll x, y;
        if (op == 3)
            x = 1LL * (rnd() % (r - l + 1)) + 1;
        else
            x = 1LL * (rnd() % vmax) + 1;
        if (op == 4)
            y = 1LL * (rnd() % vmax) + 1;
        if (op == 1)
            add(l, r, x);
        else if (op == 2)
            assign(l, r, x);
        else if (op == 3)
            printf("%lld\n", rnk(l, r, x));
        else
            printf("%lld\n", sum(l, r, x, y));
    }
    return 0;
}

數學

線性篩素數

給定一個整數 \(n\) ,求出 $[2,n] $ 之間的所有素數。

思路:prime 數組存放已經篩出的素數, \(m\) 代表素數個數(也就是說遍歷時從 \(1\) 遍歷到 \(m\) 即可),v 數組代表有沒有被標記,避免重復篩。

int v[maxn], prime[maxn], n, k, t, m;
void primes(int n)
{
    memset(v, 0, sizeof(v)); //清空標記數組
    m = 0;                   //質數個數
    for (int i = 2; i <= n; i++)
    {
        if (!v[i])                    //未被標記,i為質數
            v[i] = i, prime[++m] = i; //記錄
        for (int j = 1; j <= m; j++)
        {
            if (prime[j] > v[i] || prime[j] > n / i)
                break;                  //i有更小的質因子,或者超出n的范圍
            v[i * prime[j]] = prime[j]; //prime[j]為合數 i*prime[j]的最小質因子
        }
    }
}
int main()
{
    scanf("%d", &n);
    primes(n);
    for (int i = 1; i <= m; ++i)
        printf("%d\n", prime[i]);
}

最大公約數

① 標准

inline int gcd(int a, int b)
{
    int r;
    while (b > 0)
    {
        r = a % b;
        a = b;
        b = r;
    }
    return a;
}

② 位運算

inline int gcd(int a, int b) //a,b不能為0
{
    while (b ^= a ^= b ^= a %= b)
        ;
    return a;
}

③ 輾轉相除法

inline int gcd(int a, int b)
{
    if (b == 0)
        return a;
    else
        return gcd(b, a % b);
}

④ 三目

inline int gcd(int a, int b)
{
    return b > 0 ? gcd(b, a % b) : a;
}

⑤ 外掛(考試禁止)

#include <algorithm>
inline int gcd(int a, int b)
{
    return __gcd(a, b); //其實可以在主函數里直接用這個
}

線性求逆元

inv[0] = inv[1] = 1;
    for (register int i(2); i <= n; i++)
        inv[i] = (1ll * (mod - mod / i) * inv[mod % i]) % mod;

線性求歐拉函數

int prime[N], phi[N];
bool isprime[N];

void pre()
{
    ll cnt = 0;
    isprime[1] = 1;
    phi[1] = 1;
    for (register int i(2); i <= n; ++i)
    {
        if (!isprime[i])
        {
            prime[++cnt] = i;
            phi[i] = i - 1;
        }
        for (register int j(1); j <= cnt and i * prime[j] <= n; ++j)
        {
            isprime[i * prime[j]] = 1;
            if (i % prime[j])
                phi[i * prime[j]] = phi[i] * phi[prime[j]];
            else
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
}

矩陣加速(數列)

P1962 斐波那契數列

\(F_n = \begin{cases} 1 & (n \leq 2)\\ F_{n-1} + F_{n-2} & (n \geq 3 )\end{cases}\)

請你求出 \(F_n \text{ mod } 10^9+7\) 的值。


\(\begin{cases} F_{n+1} = 1 × F_{n} + 1 × F_{n-1} \\ F_n = 1× F_n + 0× F_{n - 1} \end{cases}\)

\( \Rightarrow \left[\begin{array}{ccc} F_{n+1}\\ F_n \end{array}\right] = \left[\begin{array}{ccc} 1 & 1\\ 1 & 0 \end{array}\right] × \left[\begin{array}{ccc} F_n\\ F_{n- 1} \end{array}\right] \)

\( \Rightarrow \left[\begin{array}{ccc} F_{n+1}\\ F_n \end{array}\right] = \left[\begin{array}{ccc} 1 & 1\\ 1 & 0 \end{array}\right] ^ {n - 1} × \left[\begin{array}{ccc} F_2\\ F_1 \end{array}\right] \)

\( \Rightarrow \left[\begin{array}{ccc} F_{n+1}\\ \color{red}{F_n} \end{array}\right] = \left[\begin{array}{ccc} 1 & 1\\ 1 & 0 \end{array}\right] ^ {n - 1} × \left[\begin{array}{ccc} 1\\ 1 \end{array}\right] \)

則初始化矩陣為 \(\left[\begin{array}{ccc}1\\1\end{array}\right]\),快速冪 \(n-1\) 次轉移矩陣 \(\left[\begin{array}{ccc} 1 & 1\\ 1 & 0 \end{array}\right]\),得到的 \(Ans_{2, 1}\) 即為答案。

創建

struct STU{
    ll m[3][3];
    STU(){memset(m, 0, sizeof m);}
    STU operator*(const STU &b) const{
        STU x;
        for(int i = 1; i <= 2; ++i)
            for(int j = 1; j <= 2; ++j)
                for(int k = 1; k <= 2; ++k)
                    x.m[i][j] = (x.m[i][j] + m[i][k] * b.m[k][j]) % mod;
        return x;
    }
} Ans, Base;

初始化

void Pre()
{
    Ans.m[1][1] = Ans.m[2][1] = 1;
    Base.m[1][1] = Base.m[1][2] = Base.m[2][1] = 1;
}

計算

void ksm(ll b)
{
    while(b)
    {
        if(b & 1)
            Ans = Ans * Base;
        Base = Base * Base;
        b >>= 1;
    }
}

int main()
{
    ll n = read();
    Pre();
    ksm(n - 1);
    printf("%lld\n", Ans.m[2][1] % mod);
    return 0;
}

分治

快速乘法取余

給定三個整數 \(a,n,mod\) ,求 \(a \times n ~\%~mod\) 的值。

inline int mult_mod(int a, int n, int mod)
{
    int ans = 0;
    while (n > 0)
    {
        if (n & 1)
            ans = (ans + a) % mod;
        a = (a + a) % mod;
        n >>= 1;
    }
    return ans;
}

這個東西好像沒有必要的樣子,貌似只需要 \((a~\%~mod)×(n~\%~mod)~\%~mod\) 即可。

快速冪

給定三個整數 \(a,b,mod\) ,求 \(a^b~\%~mod\) 的值。

ll ksm(ll a, ll b, ll mod)
{
    ll ret = 1 % mod;
    while(b)
    {
        if(b & 1)
            ret = ret * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ret;
}

LIS

求一個序列的最長上升子序列個數。
本程序采用邊讀邊處理 + 二分法。

ll f[maxn], ans = 1; //注意答案個數初始化為1

int main()
{
    ll n = read();
    for (int i = 1; i <= n; ++i)
    {
        int x = read();
        if (i == 1)
        {
            f[1] = x;
            continue;
        }
        int l = 1, r = ans, mid;
        while (l <= r)
        {
            mid = (l + r) >> 1;
            if (x <= f[mid])
                r = mid - 1;
            else
                l = mid + 1;
        }
        f[l] = x;
        if (l > ans)
            ++ans;
    }
    printf("%lld\n", ans);
    return 0;
}

lower_bound

使用前提:數列為有序數列。

①數組內

//查找范圍:[ begin , end ) ,左閉右開區間。
*lower_bound(begin, end, num);        //返回第一個 >= num 的數的數值
lower_bound(begin, end, num) - begin; // 返回下標

實際操作:
int a[100] = {0, 1, 3, 5, 7, 9, 10};
//       下標:0  1  2  3  4  5  6
int main()
{
    int x = lower_bound(a + 1, a + 6 + 1, 6) - a; //輸出下標
    int y = *lower_bound(a + 1, a + 6 + 1, 6);    //輸出值
    printf("%d %d", x, y);
    return 0;
}

輸出結果:4 7

②結構體內

結構體內使用 lower_bound 需要重載,下面我們主要對結構體中的 \(a\) 進行操作。

struct node //開結構體
{
    int a, id; //定義結構體內的兩個變量
    node() {}
    node(int x, int y) : a(x), id(y) {}
    bool operator<(const node t) const //重載
    {
        return a < t.a;
    }
} t[1001];

bool cmp(node x, node y) //快排 cmp 比較函數
{
    if (x.a < y.a)
        return 1; //結構體內按 a 由小到大排序。
    return 0;
}

int main()
{
    int n = read(); //數列中數的個數
    for (int i = 1; i <= n; ++i)
    {
        t[i].a = read(); //讀入數列
        t[i].id = i;
    }
    sort(t + 1, t + n + 1, cmp); //按小到大排序

    int x, xiabiao, ans;
    x = read();                                              //需要查找的數字
    xiabiao = lower_bound(t + 1, t + n + 1, node(x, 0)) - t; //這樣求下標
    ans = (*lower_bound(t + 1, t + n + 1, node(x, 0))).a;    //這樣求值
    printf("%d %d\n", xiabiao, ans);
    return 0;
}

輸入:
5
20 40 30 10 50
35
輸出:
4 40

另:upper_bound 的使用與 lower_bound 的使用類似,只不過是嚴格大於(>)。

動態規划

基礎模型

數字金字塔

f[i][j] = max((f[i][j] + f[i + 1][j]), (f[i][j] + f[i][j + 1]));

LCS

操作對象:兩個長度不一定相等的字符串。

例題

string s, t;
int f[maxn][maxn];

int main()
{
    cin >> s >> t;
    int ls = s.length(), lt = t.length();
    for (int i = 1; i <= ls; i++)
        for (int j = 1; j <= lt; j++)
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (s[i - 1] == t[j - 1])
                f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }
    cout << f[ls][lt] << endl;
    return 0;
}

LCIS

操作對象:兩個長度不一定相等的數列。

CF10D

const int maxn = 1005;
ll n, m, a[maxn], b[maxn], ans;
ll f[maxn][maxn], lcis[maxn][maxn];

int main()
{
    n = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    m = read();
    for (int i = 1; i <= m; ++i)
        b[i] = read();

    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1, k = 0; j <= m; ++j)
        {
            if (a[i] == b[j])
            {
                f[i][j] = f[i - 1][k] + 1;
                for (int p = 1; p <= f[i - 1][k]; ++p)
                    lcis[j][p] = lcis[k][p];
                lcis[j][f[i][j]] = a[i];
            }
            else
                f[i][j] = f[i - 1][j];
            if (b[j] < a[i] && f[i][j] > f[i][k])
                k = j;
        }
    }

    for (int i = 1; i <= m; ++i)
        if (f[n][i] > f[n][ans])
            ans = i;

    printf("%lld\n", f[n][ans]);
    for (int p = 1; p <= f[n][ans]; ++p)
        printf("%lld ", lcis[ans][p]);
    puts("");
    return 0;
}

基礎背包

01背包

背包數量為 \(V\),有 \(n\)件物品,重量為 \(w_i\),價值為 \(c_i\)。求能獲得最大價值。

ll V, n, w[10000], c[10000], f[10000];

int main()
{
    V = read();
    n = read();
    for (int i = 1; i <= n; ++i)
    {
        w[i] = read();
        c[i] = read();
    }
    for (int i = 1; i <= n; ++i)
        for (int v = V; v >= w[i]; --v)
        {
            if (f[v - w[i]] + c[i] > f[v])
                f[v] = f[v - w[i]] + c[i];
        }
    printf("%lld\n", f[V]);
    return 0;
}

01-方案數

一種物品只能選一次,組合出固定價值的方案數問題。

例題:ybt1291:數字組合

ll a[21], f[1003], n, t;
int main()
{
    n = read();
    t = read();
    for (int i = 1; i <= n; ++i)
    {
        a[i] = read();
    }
    f[0] = 1;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = t; j >= a[i]; --j)
        {
            f[j] += f[j - a[i]];
        }
    }
    printf("%lld\n", f[t]);
    return 0;
}

完全背包

一種物品可以選無限次。
只需要改一下第二層循環的循環順序就行了。

for (int i = 1; i <= n; ++i)
    for (int v = w[i]; v <= V; ++v)
    {
        if (f[v - w[i]] + c[i] > f[v])
            f[v] = f[v - w[i]] + c[i];
    }

完全-方案數

一種物品可以選無限次,組合出固定價值的方案數問題。
例題:ybt1293:買書

ll a[5], f[10002], m;

int main()
{
    m = read();
    a[1] = 10, a[2] = 20, a[3] = 50, a[4] = 100;
    f[0] = 1;
    for (int i = 1; i <= 4; ++i)
    {
        for (int j = a[i]; j <= m; ++j)
        {
            f[j] += f[j - a[i]];
        }
    }
    printf("%lld\n", f[m]);
    return 0;
}

混合背包

一種物品可以選 \(p\) 次。

ll w[31], c[31], p[31];
ll f[201], n, m;

int main()
{
    m = read();
    n = read();
    for (int i = 1; i <= n; ++i)
    {
        w[i] = read();
        c[i] = read();
        p[i] = read();
    }
    for (int i = 1; i <= n; ++i)
    {
        if (p[i] == 0) //完全
        {
            for (int j = w[i]; j <= m; ++j)
                f[j] = max(f[j], f[j - w[i]] + c[i]);
        }
        else //01和多重
        {
            for (int j = 1; j <= p[i]; ++j)
            {
                for (int k = m; k >= w[i]; --k)
                {
                    f[k] = max(f[k], f[k - w[i]] + c[i]);
                }
            }
        }
    }
    printf("%lld\n", f[m]);
    return 0;
}

二維費用背包

再加一重循環,多開一維數組即可。

例題:ybt1271:【例9.15】潛水員

ll v, u, k;
ll a[1091], b[1021], c[1092];
ll f[101][101];

int main()
{
    memset(f, 127, sizeof f);
    f[0][0] = 0;
    v = read();
    u = read();
    k = read();
    for (int i = 1; i <= k; ++i)
    {
        a[i] = read();
        b[i] = read();
        c[i] = read();
    }
    for (int i = 1; i <= k; ++i)
    {
        for (int j = v; j >= 0; --j)
        {
            for (int l = u; l >= 0; --l)
            {
                ll t1 = j + a[i];
                ll t2 = l + b[i];
                t1 = min(t1, v);
                t2 = min(t2, u);
                f[t1][t2] = min(f[t1][t2], f[j][l] + c[i]);
            }
        }
    }
    printf("%lld\n", f[v][u]);
    return 0;
}

進階dp

區間 dp

f[i][j] 中的 \(i\) 為起點,\(j\) 為終點進行 dp。

核心代碼:

for (int l = 2; l <= n; ++l) // 枚舉區間長度
{
    for (int i = 1; i + l - 1 <= n; ++i)
    {
        int j = i + l - 1;
        for (int k = i; k < j; ++k)
        {
            dp;
        }
    }
}
print();

樹形 dp

P2015 二叉蘋果樹

f[u][i] 表示以 \(u\) 為根節點的子樹選擇 \(i\) 條邊,至多保留的蘋果數目。

\(f[u][i]=max(f[u][i],f[u][i−j−1]+f[v][j]+e[i].w)\)

$ 1≤i≤min(m,sz[u]),0≤j≤min(sz[v],i−1) $

\(u\) 表示當前節點,\(v\)\(u\) 的一個子節點,\(sz[u]\) 表示 \(u\) 的子樹上的邊數,\(m\) 就是題目中要求的最多保留邊數。

void dfs(ll now, ll fath)
{
    for(int i = head[now]; i; i = t[i].nxt)
    {
        ll y = t[i].to;
        if(y == fath) continue;
        dfs(y, now);
        sz[now] += sz[y] + 1;
        for(int j = min(sz[now], m); j >= 1; --j)
            for(int k = min(1LL * j - 1, sz[y]); k >= 0; --k)
                f[now][j] = max(f[now][j], f[now][j - k - 1] + f[y][k] + t[i].w);
    }
}

斜率優化 dp

未完成,待補充。

例題:[HNOI2008]玩具裝箱

const ll N = 5e4 + 2;

ll n, L;
ll h, t, Q[N];
double sum[N], f[N];

double A(ll i) { return (double)sum[i] + i; }
double B(ll i) { return (double)sum[i] + i + 1.0 * L + 1.0; }
double X(ll i) { return (double)B(i); }
double Y(ll i) { return (double)f[i] + B(i) * B(i); }

double K(ll i, ll j)
{
    return ((Y(i) - Y(j))) / ((X(i) - X(j)));
}

void Main()
{
    n = read(), L = read();
    for (int i = 1; i <= n; ++i)
    {
        scanf("%lf", &sum[i]);
        sum[i] += sum[i - 1];
    }
    h = t = 1;
    for (int i = 1; i <= n; ++i)
    {
        while (h < t and K(Q[h], Q[h + 1]) < 2 * A(i))
            ++h;
        f[i] = f[Q[h]] + (A(i) - B(Q[h])) * (A(i) - B(Q[h]));
        while (h < t and K(i, Q[t - 1]) < K(Q[t - 1], Q[t]))
            --t;
        Q[++t] = i;
    }
    printf("%.0lf\n", f[n]);
}

圖論

前置

鏈式前向星存圖

ll head[maxn];

struct node
{
    ll nxt, to, w;
} t[maxn];

void add(const ll u, const ll v, const ll w)
{
    t[++tot].to = v;
    t[tot].w = w;
    t[tot].nxt = head[u];
    head[u] = tot;
}

vector 存圖

struct node{
    ll to, w;
};

vector<node> t[maxn];

void add(const int u, const int v, const int w)
{
    t[u].push_back((node){v, w});
}

圖的遍歷

存圖形式采用 \(\text{vector}\) 存圖

ans 存儲遍歷順序。

DFS

void dfs(ll s)
{
    vis[s] = 1;
    ans.push_back(s);
    for (int i = 0; i < t[s].size(); ++i)
        if (!vis[t[s][i]])
            dfs(t[s][i]);
}

BFS

void bfs(ll s)
{
    queue<ll> q;
    q.push(s);
    vis[s] = 1;
    while (!q.empty())
    {
        ll x = q.front();
        ans.push_back(x);
        q.pop();
        for (ll i = 0; i < t[x].size(); ++i)
        {
            if (!vis[t[x][i]])
            {
                q.push(t[x][i]);
                vis[t[x][i]] = 1;
            }
        }
    }
}

Dijkstra 最短路

求單源 \(s\) 到任意一點的最短路徑。最短路徑保存在數組 dis 中。

鏈式前向星

#include <queue>
priority_queue<pair<ll, ll>> q;
void dijkstra(int s)
{
    memset(dis, 0x3f, sizeof(dis)); //初始邊無限大
    memset(vis, 0, sizeof(vis));          //結點初始均為訪問
    dis[s] = 0;                           //起點到自己距離為0
    q.push(make_pair(0, s));              //起點進隊
    while (!q.empty())
    {
        x = q.top().second;
        q.pop();                          //初始結點入隊
        if (vis[x])
            continue;                     //如果走過,直接跳過
        vis[x] = 1;                       //標記已訪問
        for (ll i = head[x]; i != -1; i = t[i].nxt)
        {
            ll y = t[i].to, z = t[i].w;
            if (dis[y] > dis[x] + z)
            {
                dis[y] = dis[x] + z;           //更新起點到y最短路
                q.push(make_pair(-dis[y], y)); //d[y]相反數入隊,轉小根堆
            }
        }
    }
}
int main()
{
    for (int i = 1; i <= n; ++i)
        head[i] = -1;
    ...
}
//后面省略

vector

void dj(int s)
{
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof vis);
    dis[s] = 0;
    q.push(make_pair(0, s));
    while (!q.empty())
    {
        ll x = q.top().second;
        q.pop();
        if (vis[x])
            continue;
        vis[x] = 1;
        for (int i = 0; i < t[x].size(); ++i)
        {
            ll y = t[x][i].to, z = t[x][i].w;
            if (dis[y] > dis[x] + z)
            {
                dis[y] = dis[x] + z;
                q.push(make_pair(-dis[y], y));
            }
        }
    }
}

SPFA

SPFA能處理負邊權,可以判斷負環。也可以求最長路。

最短路

#include <queue>
queue<int> q;
void SPFA(int s)
{
    fill(dis + 1, dis + 1 + n, 2147483647); //初始邊無限大
    memset(vis, 0, sizeof vis);
    dis[s] = 0;
    q.push(s);
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for (int i = head[x]; i != -1; i = t[i].nxt)
        {
            int y = t[i].to, z = t[i].w;
            if (dis[y] > dis[x] + z)
            {
                dis[y] = dis[x] + z;
                if (vis[y] == 0)
                {
                    q.push(y);
                    vis[y] = 1;
                }
            }
        }
    }
}

最長路

可依據最短路代碼進行修改。

1. `dis` 數組賦初值時,如果沒有負邊權就賦 $-1$ ,如果有負邊權就賦無限小。
  1. dis[y]>dis[x]+z 中的 > 改成 <

判斷負環

可在最短路代碼基礎上進行修改。需新加入一個數組 cnt ,專門記錄負環。

補充代碼:

ll cnt[maxn]; //專門記錄負環
void SPFA().....if (dis[y] > dis[x] + z)
{
    dis[y] = dis[x] + z;
    cnt[y]++;
    if (cnt[y] >= n + 1) //出現超過n次表示就有負環
    {
        ans = 1; //ans=1代表有負環。
        return;
    }
    if (vis[y] == 0)
    {
        q.push(y);
        vis[y] = 1;
    }
}

Floyd 全源最短路

inline void floyd()
{
    for (k = 1; k <= n; k++)
        for (i = 1; i <= n; i++)
            for (j = 1; j <= n; j++)
                if (e[i][j] > e[i][k] + e[k][j])
                    e[i][j] = e[i][k] + e[k][j];
}

次短路

嚴格次短路

ll dis[N][2];
bool vis[N];
queue<ll> q;

void SPFA(ll s)
{
    memset(dis, 0x3f, sizeof dis);
    q.push(s);
    vis[s] = 1;
    dis[s][0] = 0;
    while(!q.empty())
    {
        ll x = q.front();
        q.pop();
        vis[x] = 0;
        for(int i = head[x]; i; i= t[i].nxt)
        {
            ll y = t[i].to, z = t[i].w;
            if(dis[y][0] > dis[x][0] + z)
            {
                dis[y][1] = dis[y][0];
                dis[y][0] = dis[x][0] + z;
                if(vis[y] == 0)
                    vis[y] = 1, q.push(y);
            }
            if(dis[y][1] > dis[x][0] + z and dis[x][0] + z > dis[y][0])
            {
                dis[y][1] = dis[x][0] + z;
                if(vis[y] == 0)
                    vis[y] = 1, q.push(y);
            }
            if(dis[y][1] > dis[x][1] + z)
            {
                dis[y][1] = dis[x][1] + z;
                if(vis[y] == 0)
                    vis[y] = 1, q.push(y);
            }
        }
    }
}

非嚴格次短路

queue<ll> q;
ll fr[N], fro[N], dis[N];
bool vis[N];

void SPFA(ll s, ll u, ll to)
{
    for (int i = 1; i <= n; ++i)
        dis[i] = INF, vis[i] = 0;
        
    dis[s] = 0;
    vis[s] = 1;
    q.push(s);
    while (!q.empty())
    {
        ll x = q.front();
        q.pop();
        vis[x] = 0;
        for (int i = head[x]; i; i = t[i].nxt)
        {
            ll y = t[i].to, z = t[i].w;
            if ((x == u and y == to) || (x == to and y == u))
                continue;
            if (dis[y] > dis[x] + z)
            {
                dis[y] = dis[x] + z;
                fr[y] = x;
                if (vis[y] == 0)
                    vis[y] = 1, q.push(y);
            }
        }
    }
}

int main()
{
    存邊;

    SPFA(1, 0, 0);

    ll now = n, ans = INF;
    for (int i = 1; i <= n; ++i)
        fro[i] = fr[i];

    while (fro[now])
    {
        SPFA(1, now, fro[now]);
        ans = min(ans, dis[n]);
        now = fro[now];
    }

    W((ans == INF) ? -1 : ans, '\n');
    return 0;
}


並查集

\(n\) 代表元素個數,\(m\) 為操作數。

\(opt=1\) 時,合並集合 \(a,b\)\(opt=2\) 時,如果 \(a,b\) 在同一集合,輸出 Y 否則輸出 N

int find(int k)
{
    if (f[k] == k)
        return k;
    return f[k] = find(f[k]);
}

int main()
{
    n = read();
    m = read();
    for (int i = 1; i <= n; ++i)
        f[i] = i; //初始化自己的老大是自己

    for (int i = 1; i <= m; ++i)
    {
        int opt, a, b;
        opt = read();
        a = read();
        b = read();
        if (opt == 1)
            f[find(a)] = find(b);
        else
        {
            if (find(a) == find(b))
                printf("Y\n");
            else
                printf("N\n");
        }
    }
    return 0;
}

LCA

P3379 【模板】最近公共祖先(LCA)

鄰接表存圖。

struct node{...};
void add(...){}

ll dep[500010], fa[500010][25];
ll head[500010], tot;
ll n, m, s;

ll dep[N], fa[N][23];
void dfs(ll now, ll fath)
{
    dep[now] = dep[fath] + 1;
    fa[now][0] = fath;
    for(int i = 1; i <= 22; ++i)
        fa[now][i] = fa[fa[now][i - 1]][i - 1];
    for(int i = head[now]; i; i = t[i].nxt)
        if(t[i].to != fath)
            dfs(t[i].to, now);
}

ll LCA(ll x, ll y)
{   
    if(dep[x] < dep[y]) swap(x, y);
    for(int k = 22; k >= 0; --k)
        if(dep[fa[x][k]] >= dep[y])
            x = fa[x][k];
    if(x == y) return x;
    for(int k = 22; k >= 0; --k)
        if(fa[x][k] != fa[y][k])
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];    
}

int main()
{
    n = read();
    m = read();
    s = read();
    for (int i = 1; i <= n; ++i)
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
        
    for (int i = 1; i < n; ++i)
    {
        ll x = read(), y = read();
        add(x, y);
        add(y, x);
    }
    
    dfs(s, 0);
    for (int i = 1; i <= m; ++i)
    {
        ll x = read(), y = read();
        printf("%lld\n", LCA(x, y));
    }
    return 0;
}

最小生成樹

Kruskal

前置:並查集

const ll N = 2e5 + 3;
ll n, m, fa[N], ans;

struct node{
    ll x, y, w;
}t[N];

bool cmp(node a, node b)
{
    return a.w < b.w;
}

ll F(ll x)
{
    if(fa[x] == x) return x;
    return fa[x] = F(fa[x]);
}

int main()
{
    n = read(), m = read();

    for(int i = 1; i <= m; ++i)
        t[i].x = read(), t[i].y = read(), t[i].w = read();
    sort(t + 1, t + m + 1, cmp);
    for(int i = 1; i <= n; ++i)
        fa[i] = i;

    for(int i = 1; i <= m; ++i)
    {
        if(F(t[i].x) == F(t[i].y)) continue;
        fa[F(t[i].x)] = F(t[i].y);
        ans += t[i].w;
    }

    for(int i = 2; i <= n; ++i)
    {
        if(F(i) != F(1)) // if Lian Tong
        {
            puts("orz");
            return 0;
        }
    }

    printf("%lld\n", ans);
    return 0;
}

Prim

int ans, cnt, now = 1; //Prim
void prim()
{
    for (int i = 2; i <= n; ++i)
        dis[i] = MAXN;

    for (int i = head[1]; i; i = t[i].nxt)
        dis[t[i].to] = min(dis[t[i].to], t[i].w);

    while (++cnt < n)
    {
        int minn = MAXN;
        vis[now] = 1;
        for (int i = 1; i <= n; ++i)
        {
            if (vis[i])
                continue;
            if (minn > dis[i])
            {
                minn = dis[i];
                now = i;
            }
        }

        ans += minn;

        for (int i = head[now]; i; i = t[i].nxt)
        {
            int y = t[i].to, z = t[i].w;
            if (vis[y])
                continue;
            if (dis[y] > z)
            {
                dis[y] = z;
            }
        }
    }
}

拓撲排序

ll ans[100] ,cnt; //拓撲序列及其元素個數
ll deg[100];       //所有點的入度

void topsort()
{
    queue<ll> q;
    for (int i = 1; i <= n; ++i)
        if (deg[i] == 0) //尋找最開始入度就為0的點
            q.push(i);   //入隊

    while (!q.empty())
    {
        ll x = q.front();
        q.pop();
        ans[++cnt] = x; //把隊首的元素放進拓撲序列
        for (int i = head[x]; i; i = t[i].nxt)
        {
            ll y = t[i].to;    //尋找鄰邊
            if (--deg[y] == 0) //鄰邊的入度-1,並且判斷減后的入度是否為0
                q.push(y);     //如果為0就入隊
        }
    }
}

int main()
{
    n = read();
    m = read(); //點,邊
    for (int i = 1; i <= m; ++i)
    {
        ll x = read(), y = read();
        add(x, y);
        deg[v]++; //入度++
    }

    topsort(); //拓撲排序

    if (cnt < n) //拓撲序列的元素個數小於點數,說明有環
        puts("有環");
    else
        puts("無環");

    for (int i = 1; i <= cnt; ++i)
        printf("%lld ", ans[i]); //輸出拓撲序列

    return 0;
}

Tarjan

縮點

P3387 【模板】縮點

const ll N = 10002;

ll head[N], tott, w[N], n, m;
ll dfn[N], low[N], st[N], top, totx, cntscc;
ll belong[N], in[N], f[N];
bool vis[N], vis_out[N];

struct node{
    ll to, nxt;
} t[N * 10];

void add(ll u, ll v)
{
    t[++tott].nxt = head[u];
    t[tott].to = v;
    head[u] = tott;
}

struct Scc{
    ll w;
    vector<ll> to;
    // vector<ll> have;
} SCC[N * 10];

void Tarjan(ll now)
{
    low[now] = dfn[now] = ++totx;
    st[++top] = now;
    vis[now] = 1;
    for(int i = head[now]; i;i = t[i].nxt)
    {
        ll y = t[i].to;
        if(!dfn[y])
        {
            Tarjan(y);
            low[now] = min(low[now], low[y]);
        }
        else if(vis[y])
            low[now] = min(low[now], low[y]);
    }
    if(dfn[now] == low[now])
    {
        ll y;
        ++cntscc;
        while(y = st[top--])
        {
            vis[y] = 0;
            belong[y] = cntscc;
            SCC[cntscc].w += w[y];
            // SCC[cntscc].have.push_back(y); 存放此 SCC 里有哪些點
            if(now == y) break;
        }
    }
    return;
}

ll topo()
{
    queue<ll> q;
    for(int i = 1; i <= cntscc; ++i)
        for(int j = 0; j < SCC[i].to.size(); ++j)
            ++in[SCC[i].to[j]];
    for(int i = 1; i <= cntscc; ++i)
        if(in[i] == 0)
            q.push(i), f[i] = SCC[i].w;
        
    while(!q.empty())
    {
        ll x = q.front();
        q.pop();
        for(int j = 0; j < SCC[x].to.size(); ++j)
        {
            ll y = SCC[x].to[j];
            f[y] = max(f[y], f[x] + SCC[y].w);
            if(--in[y] == 0)
                q.push(y);
        }
    }
    ll ans = 0;
    for(int i = 1; i <= cntscc; ++i)
        ans = max(f[i], ans);
    return ans;
}

int main()
{
    n = read(), m = read();

    for(int i = 1; i <= n; ++i)
        w[i] = read(); 

    for(int i = 1; i <= m; ++i)
    {
        ll x = read(), y = read();
        if(x == y) continue; //判斷重邊
        add(x, y);
    }
    
    for(int i = 1; i <= n; ++i)
    {
        if(!dfn[i])
            Tarjan(i);
    }

    for(int i = 1; i <= n; ++i) // 重構圖
        for(int j = head[i]; j; j = t[j].nxt)
        {
            ll y = t[j].to;
            if(belong[i] != belong[y])
                SCC[belong[i]].to.push_back(belong[y]);
        }

    printf("%lld\n", topo());
    return 0;
}

割點

P3388 【模板】割點(割頂)

ll totans, cnt, dfn[N], low[N];
ll cnt, dfn[N], low[N];
bool is_ans[N];

void Tarjan(ll now, ll fath)
{
    low[now] = dfn[now] = ++cnt;
    ll totson = 0;
    for(int i = head[now]; i; i = t[i].nxt)
    {
        ll y = t[i].to;
        if(!dfn[y])
        {
            ++totson;
            Tarjan(y, now);
            low[now] = min(low[now], low[y]);
            if(now != fath and low[y] >= dfn[now] and !is_ans[now])
            {
                ++totans;
                is_ans[now] = 1;
            }
        }
        else if(y != fath)
            low[now] = min(low[now], dfn[y]);
    }
    if(now == fath and totson >= 2 and !is_ans[now])
    {
        ++totans;
        is_ans[now] = 1;
    }
}

int main()
{
    n = read(), m = read();
    for(int i = 1; i <= m; ++i)
    {
        ll a = read(), b = read();
        add(a, b);
        add(b, a);
    }
    for(int i = 1; i <= n; ++i)
        if(!dfn[i])
            Tarjan(i, i);
            
    W(totans, '\n');
    for(int i = 1; i <= n; ++i)
        if(is_ans[i]) W(i, ' ');
    return 0;
}

字符串

快速讀入

可以根據題目描述自行修改。

void Init()
{
    char ch;
    ch = getchar();
    while (ch < 'A' || ch > 'Z')
        ch = getchar();
    while (ch >= 'A' && ch <= 'Z')
    {
        A[++lena] = ch;
        ch = getchar();
    }

    while (ch < 'A' || ch > 'Z')
        ch = getchar();
    while (ch >= 'A' && ch <= 'Z')
    {
        B[++lenb] = ch;
        ch = getchar();
    }
}

KMP

模板題

\(A\) 為大串,\(B\) 為小串。

\(next\) 數組

void make_nxt()
{
    j = 0;
    for (int i = 2; i <= lenb; ++i)
    {
        while (B[i] != B[j + 1] and j)
            j = nxt[j];
        if (B[i] == B[j + 1])
            ++j;
        nxt[i] = j;
    }
}

匹配

void check()
{
    j = 0;
    for (int i = 1; i <= lena; i++)
    {
        while (A[i] != B[j + 1] and j)
            j = nxt[j];
        if (A[i] == B[j + 1])
            ++j;
        if (j == lenb)
        {
            printf("%lld\n", i - lenb + 1);
            j = nxt[j];
        }
    }
}

AC自動機

大佬博客

模板1

判斷多個模式串 \(s_i\) 是否在樣本串 \(t\) 里出現過。

const int N = 1000005;
int n, len, tot, ch[N][30], End[N], fail[N];
char s[N];
queue<int> q;

void insert()
{
    len = strlen(s + 1);
    int now = 0;
    for (int i = 1; i <= len; i++)
    {
        int c = s[i] - 'a';
        if (!ch[now][c])
            ch[now][c] = ++tot;
        now = ch[now][c];
    }

    End[now]++;
}

void make_AC()
{
    for (int i = 0; i < 26; i++)
    {
        if (ch[0][i])
        {
            fail[ch[0][i]] = 0; //特殊處理,避免跳回自己
            q.push(ch[0][i]);
        }
    }
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for (int i = 0; i < 26; i++)
        {
            if (ch[x][i])
            {
                fail[ch[x][i]] = ch[fail[x]][i];
                q.push(ch[x][i]);
            }
            else
                ch[x][i] = ch[fail[x]][i];
        }
    }
}

int check_tot()
{
    len = strlen(s + 1);
    int now = 0, ans = 0;
    for (int i = 1; i <= len; i++)
    {
        now = ch[now][s[i] - 'a'];
        for (int t = now; t > 0 && End[t] != -1; t = fail[t])
        {
            ans += End[t];
            End[t] = -1;
        }
    }
    return ans;
}

int main()
{
    n = read();
    for (int i = 1; i <= n; i++)
    {
        scanf("%s", s + 1);
        insert();
    }

    make_AC();

    scanf("%s", s + 1);
    printf("%d\n", check_tot());

    return 0;
}

模板2

輸出模式串最多出現的次數,輸出出現次數最多的模式串。

const int N = 1000005;
int n, len, cnt, tot, maxn;
int ch[N][30], End[N], fail[N];
int num[210], sum[210];
char t[N], s[210][210];
queue<int> q;

void C()
{
    while (q.size())
        q.pop();
    maxn = 0;
    cnt = 0;
    tot = 0;
    memset(ch, 0, sizeof(ch));
    memset(sum, 0, sizeof(sum));
    memset(End, 0, sizeof(End));
    memset(ch, 0, sizeof(ch));
}

void insert(ll k)
{
    len = strlen(s[k] + 1);
    int now = 0;
    for (int i = 1; i <= len; ++i)
    {
        int c = s[k][i] - 'a';
        if (!ch[now][c])
            ch[now][c] = ++tot;
        now = ch[now][c];
    }
    End[now] = k;
}

void make_AC()
{
    for (int i = 0; i < 26; i++)
    {
        if (ch[0][i])
        {
            fail[ch[0][i]] = 0; //特殊處理,避免跳回自己
            q.push(ch[0][i]);
        }
    }
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for (int i = 0; i < 26; i++)
        {
            if (ch[x][i])
            {
                fail[ch[x][i]] = ch[fail[x]][i];
                q.push(ch[x][i]);
            }
            else
                ch[x][i] = ch[fail[x]][i];
        }
    }
}

void check()
{
    len = strlen(t + 1);
    int now = 0;
    for (int i = 1; i <= len; i++)
    {
        now = ch[now][t[i] - 'a'];
        for (int t = now; t > 0; t = fail[t])
        {
            ++sum[End[t]];
        }
    }
}

int main()
{
    while (1)
    {
        n = read();
        if (n == 0)
            return 0;

        for (int i = 1; i <= n; ++i)
        {
            scanf("%s", s[i] + 1);
            insert(i);
        }

        make_AC();
        scanf("%s", t + 1);
        check();

        for (int i = 1; i <= n; ++i)
        {
            if (sum[i] > maxn)
            {
                maxn = sum[i];
                cnt = 1;
                num[cnt] = i;
            }
            else if (sum[i] == maxn)
            {
                num[++cnt] = i;
            }
        }

        printf("%d\n", maxn);

        for (int i = 1; i <= cnt; i++)
        {
            len = strlen(s[num[i]] + 1);
            for (int j = 1; j <= len; j++)
            {
                printf("%c", s[num[i]][j]);
            }
            puts("");
        }

        C();
    }
    return 0;
}

模板3

求出多個模式串 \(s_i\) 是否在樣本串 \(t\) 里出現的次數。

const int N = 2000005;
int n, len, cnt, tot, maxn;
int ch[N][30], End[N], fail[N], in[N], ans[N];
int num[210], sum[210], Map[N], f[N];
char t[N], t2[N];
queue<int> q, q2;
vector<char> s[N];

void insert(int k)
{
    int now = 0;
    for (int i = 0; i < s[k].size(); i++)
    {
        int c = s[k][i] - 'a';
        if (!ch[now][c])
            ch[now][c] = ++tot;
        now = ch[now][c];
    }
    if (!End[now])
        End[now] = k;
    Map[k] = End[now];
}

void make_AC()
{
    for (int i = 0; i < 26; i++)
    {
        if (ch[0][i])
        {
            fail[ch[0][i]] = 0;
            q.push(ch[0][i]);
        }
    }
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for (int i = 0; i < 26; i++)
        {
            if (ch[x][i])
            {
                fail[ch[x][i]] = ch[fail[x]][i];
                in[fail[ch[x][i]]]++;
                q.push(ch[x][i]);
            }
            else
                ch[x][i] = ch[fail[x]][i];
        }
    }
}

void check()
{
    int now = 0, len = strlen(t + 1);
    for (int i = 1; i <= len; i++)
    {
        int c = t[i] - 'a';
        now = ch[now][c];
        f[now]++;
    }
}

void top()
{
    for (int i = 1; i <= tot; i++)
    {
        if (in[i] == 0)
            q2.push(i);
    }

    while (!q2.empty())
    {
        int x = q2.front();
        q2.pop();
        in[fail[x]]--;
        ans[End[x]] += f[x];
        f[fail[x]] += f[x];
        if (in[fail[x]] == 0)
        {
            q2.push(fail[x]);
        }
    }
}

int main()
{
    n = read();
    for (int i = 1; i <= n; ++i)
    {
        scanf("%s", t2 + 1);
        len = strlen(t2 + 1);
        for (int j = 1; j <= len; ++j)
            s[i].push_back(t2[j]);
        insert(i);
    }
    make_AC();
    scanf("%s", t + 1);
    check();
    top();
    for (int i = 1; i <= n; i++)
        printf("%d\n", ans[Map[i]]);

    return 0;
}

擴展 KMP

對於一個長度為 \(n\) 的字符串 \(s\),定義函數 \(z[i]\) 表示 \(s\)\(s[i,n-1]\) (即以 \(s[i]\) 開頭的后綴)的最長公共前綴(LCP)的長度。\(z\) 被稱作為 \(s\)Z 函數。特別地,\(z[0]=[0]\)

本代碼中 \(z[0]=|B|\)


const int MAXN = 2e7 + 5;
char B[MAXN], A[MAXN];
int lenb, lena, z[MAXN], ext[MAXN];

void getZ() //求 z 數組
{
    z[0] = lenb;
    int now = 0;
    while (now + 1 < lenb && B[now] == B[now + 1])
        now++;
    z[1] = now;
    int p0 = 1;
    for (int i = 2; i < lenb; ++i)
    {
        if (i + z[i - p0] < p0 + z[p0])
        {
            z[i] = z[i - p0]; //第一種情況
        }
        else
        {
            now = p0 + z[p0] - i;
            now = max(now, 0);
            while (now + i < lenb && B[now] == B[now + i])
                now++;
            z[i] = now;
            p0 = i;
        }
    }
}

void exkmp() //求 B 與 A 的每一個后綴的 LCP 長度數組
{
    int now = 0;
    while (now < lenb && now < lena && B[now] == A[now])
        now++;
    ext[0] = now;
    int p0 = 0;
    for (int i = 1; i < lena; ++i)
    {
        if (i + z[i - p0] < p0 + ext[p0])
            ext[i] = z[i - p0];
        else
        {
            now = p0 + ext[p0] - i;
            now = max(now, 0);
            while (now < lenb && now + i < lena && B[now] == A[now + i])
                now++;
            ext[i] = now;
            p0 = i;
        }
    }
}

int main()
{
    scanf("%s%s", A, B);
    lena = strlen(A); //文本串
    lenb = strlen(B); //模式串

    getZ();
    exkmp();

    得到 z[], p[];
    
}

STL

string

#include<cstring>
string s1,s2;

s1 + s2;    // 將兩個字符串拼接
[cur];      // 訪問下標
s1.size();  // 返回字符串長度
s1.append(s2);          // 將 s2 添加到 s1 末尾
s1.replace(pos, n, s2); // 刪除從 pos 開始的 n 個字符,然后在 pos 處插入串 s
s1.erase(pos, n);       // 刪除從 pos 開始的 n 個字符
s1.insert(pos, s2);     // 在 pos 位置插入字符串 s
s1.substr(start, len);  // 從 start 截取一個長度為 len 的字符串
s1.find(char,st = 0);  // 查找並返回從 start 開始的字符 ch 的位置
s1.rfind(ch);           //從末尾開始,查找並返回第一個找到的字符 ch 的位置
// 找不到則返回 -1

queue

先進先出

#include<queue>

queue<int> q; 
// priority_queue<int> q(從大到小排序);

q.empty();      // 判斷隊列是否為空
q.size();       // 返回隊列長度
q.push(item);   // 對於 queue,在隊尾壓入一個新元素
                // 對於 priority_queue,在基於優先級的適當位置插入新元素
q.pop();        // 彈出隊首元素

// queue only:
q.front();      //返回隊首元素的值,但不刪除該元素
q.back();       //返回隊尾元素的值,但不刪除該元素
     
// priority_queue only:
q.top();        //返回具有最高優先級的元素值,但不刪除該元素

stack

先進后出

#include<set>
stack<int> s; 
stack<int, vector<int> > stk;  // 覆蓋基礎容器類型,使用vector實現stk
s.empty();      // 判斷 stack 是否為空,為空返回 true,否則返回 false
s.size();       // 返回 stack 中元素的個數
s.pop();        // 刪除棧頂元素,但不返回其值
s.top();        // 返回棧頂元素的值,但不刪除此元素
s.push(item);   // 在棧頂壓入新元素 item

vector

#include <vector>
vector<int> v;

/* 操作 */
v.clear();  // 清空
v.swap(v1); // 和另一個 vector 進行交換,常數復雜度

v.push_back(k);     // 從末尾插入
v.emplace_back(k);  // 從末尾插入,更快
v.pop_back();       // 彈出末尾元素

v.insert(v.begin() + pos, k);   // 在下標為 pos 之前插入 k
v.emplace(it, k);   // 同上面的 insert,更快,但只能插入一個元素
v.insert(it, n, k); // 在 it 之前插入 n 個 k
v.insert(it, v1.begin(), v1.end()); // 在 it 之前插入另一個 vector 中的一段(左閉右開)
v.insert(it, {1, 2, 3, 4, 5});  // 顯而易見
v.erase(it);        // 刪除 it 指的元素,合並左右部分
v.erase(it, it2);   // 刪除 [it, it2) 的元素,合並左右部分

/* 查詢 */
v[k];       // 訪問第 k 個元素
v.front();  // 返回首元素
v.back();   // 返回尾元素
v.begin();  // 返回首元素的迭代器
v.end();    // 返回數組尾端占位符的迭代器(空)
v.empty();  // 返回 vector 是否為空
v.size();   // 返回 vector 元素個數

/* 遍歷 */
for(int i = 0; i < v.size(); ++i)
    v[i];

list

鏈表

#include <list>
list<ll> l, l2;
list<ll>::iterator it;

/* 操作 */
l.clear();      // 清空
l.insert(it, 0);// 在迭代器前面插入一個元素,迭代器指向原來的元素
l.erase(it);    // 刪除迭代器指向的元素
l.remove(0);    // 在 list 刪除某一種元素(全刪)
l.push_back(0); // 在 list 的末尾添加一個元素   
l.push_front(0);// 在 list 的頭部添加一個元素 
l.pop_back();   // 刪除最后一個元素 
l.pop_front();  // 刪除第一個元素 
l.merge(l2);    // 合並兩個 list 
l.reverse();    // 把 list 的元素倒轉 
l.sort();       // 給 list 排序 
l.swap(l2);     // 交換兩個 list 
l.unique();     // 刪除 list 中重復的元素

/* 查詢 */
l.begin();      // 返回指向第一個元素的迭代器 
l.end();        // 返回末尾的迭代器 
l.front();      // 返回第一個元素 
l.back();       // 返回最后一個元素 
l.empty();      // 如果 list 是空的則返回 1
l.size();       // 返回 list 中的元素個數 

/* 遍歷 */
for(it = l.begin(); it != l.end(); ++it)
    *it;

set

自動從小到大排序,自動去重。

set<int> s;
// multiset<int> s (不去重)
set<int>::const_iterator iter; // 迭代器 

s.insert();   // 插入
s.erase();    // 若參數為元素值,則刪除所有該元素值
              // 若參數為迭代器,則刪除該迭代器指向的值
s.empty();    // 判斷 set 是否為空,為空返回 true,否則返回 false
s.count();    // 返回某個值元素的個數
s.clear();    // 清除所有元素
s.find();     // 查找某元素,找到則返回其迭代器,否則返回 s.end()
s.begin();    // 返回指向第一個元素的迭代器
--s.end();    // 返回指向最后一個元素的迭代器
*s.begin();   // 返回指向第一個元素的值
*--s.end();   // 返回指向最后一個元素的值
              // 區間形式為 [ begin , end ) ,所以 end 要自減
s.size();     // 返回集合中元素的個數
*s.lower_bound(k);    // 返回第一個大於等於k的元素值
*s.upper_bound(k);    // 返回第一個大於k的元素值 (后繼)
              // 如果沒有符合條件的值,則輸出 s.size()

/* 遍歷 */
for(iter = s.begin() ; iter != s.end() ; ++iter)
    *iter; // 使用迭代器遍歷 

unordered_set

不排序,\(O(1)\) 查找。

#include <unordered_set>
// #include <unordered_multiset>
unordered_set<ll> s;
// unordered_multiser<ll> s;

s.insert(); // 在開頭插入某元素
s.find();   // 查找,返回迭代器
s.count();  // 返回某個值元素的個數

/* 遍歷 */
for(iter = s.begin() ; iter != s.end() ; ++iter)
    *iter; 
// 注意:新插入的元素遍歷時先被掃到。

map

#include<map>
map<string, int> m;// string 是 key,int 是 value。

/* 基本操作 */
m.size();   // 輸出元素個數
m.empty();  // 如果 map 為空則返回 true
m.clear();  // 刪除所有元素

/* 插入 */
m["AKIOI"] = 10;
m.insert(make_pair("AKIOI", 10));

/* 修改 */
m["AKIOI"] = 10;
m.find("AKIOI")->second = ...;

/* 查找 */
m["AKIOI"];
m.find("AKIOI")->second;

/* 遍歷 */
它會按照 key 排序
for(auto it = mp.begin(); it != mp.end(); ++it) 
    cout << it->second << ' ';

multimap

#include <map>
multimap<int, string> mp; // 可重

mp.size(), mp.empty(), mp.clear(); // 常規操作
mp.count(k) // 找 key 為 k 的個數
mp.find(k)  // 返回第一個插入的 k 的迭代器
mp.erase(k) // 刪除所有鍵值為 k 的元素

/* 插入 */
mp.insert(make_pair(int, string)); // 只能用 make_pair 構造鍵值對

/* 修改 */
m.find(k)->second = ...; // 修改第一個插入的 key 為 k 的
for(auto it = mp.find(k); it->first == k; ++it) // 修改 key 為 k,值為自己選的
    if(it->second == "I") it->second = "Love"; 

/* 查找 */
mp.find(k)->second;

/* 遍歷 */
for(auto it = mp.begin(); it != mp.end(); ++it) 
    cout << it->second << ' ';

unordered_map

#include<unordered_map>
用法:與 map 差別不大。
優點:因為內部實現了哈希表,因此其查找速度非常的快
缺點:哈希表的建立比較耗費時間
適用處:對於查找問題,unordered_map會更加高效一些,因此遇到查找問題,常會考慮一下用 unordered_map

bitset

bitset<100> b;
bitset<100> f;

b = 10, f = 11;
b[0]; // 訪問某一位

/* 相同大小的 bitset 可以進行運算符操作 */
/* == != & &= | |= ^ ^= ~ << <<= >> >>=  */

b.count();  // 返回 1 的數量
b.size();   // 返回 bitset 的大小
b.any();    // 若有 1, 返回 1
b.none();   // 若無 1, 返回 1
b.all();    // 若全是 1, 返回 1
b.set();    // 全部設為 1
b.set(0, 0);// 將第 pos 位設為 k
b.reset();  // 全部設為 0
b.flip();   // 翻轉每一位
b.flip(0);  // 翻轉某一位

rope

rope 的復制操作是 \(O(\log n)\) 的,可以較輕松地實現可持久化。

想要使用 rope,需要在頭文件中加入兩行:

#include <ext/rope>
using namespace __gnu_cxx;

定義字符串類型的 rope,叫做 crope,要這樣定義:

crope a;

支持的操作:

a.push_back(x);  // 在 a 的末尾添加字符串 x
a.insert(k, x);  // 在 a 的第 k 個字符后加入字符串 x
a.erase(k, x);   // 在 a 的第 k 個字符后刪除 x 個字符
a.replace(k, x); // 將 a 的第 k 個字符后 x 的長度個字符刪除,並插入 x
a.substr(k, x);  // 獲取 a 的第 k 個字符后的長度為 x 的字符串
a.at(k);         // 獲取 a 的第 k 個字符(從 0 開始)


免責聲明!

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



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