~图论模板

并查集

struct dsu {
private:
    // number of nodes
    int n;
    // root node: -1 * component size
    // otherwise: parent
    std::vector<int> pa;
public:
    dsu(int n_ = 0) : n(n_), pa(n_, -1) {}
    // find node x's parent
    int find(int x) {
        return pa[x] < 0 ? x : pa[x] = find(pa[x]);
    }
    // merge node x and node y 
    // if x and y had already in the same component, return false, otherwise return true
    // Implement (union by size) + (path compression)
    bool unite(int x, int y) {
        int xr = find(x), yr = find(y);
        if (xr != yr) {
            if (-pa[xr] < -pa[yr]) std::swap(xr, yr);
            pa[xr] += pa[yr];
            pa[yr] = xr; // y -> x
            return true;
        }
        return false;
    }
    // size of the connected component that contains the vertex x
    int size(int x) {
        return -pa[find(x)];
    }
};

最短路

namespace Dijkstra {
#define ll long long
    static constexpr ll INF = 1e18;
    int n, m; // 点数 边数
    struct edge {
        int to;  // 点
        ll val; // 边权
        edge(int to_ = 0, ll val_ = 0) :to(to_), val(val_) {}
        bool operator < (const edge& k) const { return val > k.val; }
    };
    vector<vector<edge>> g;
    void init() { // 建图操作需要根据题意修改
        cin >> n >> m;
        g.resize(n);
        for (int i = 0; i < m; i++) {
            int u, v, w;
            cin >> u >> v >> w;
            --u, --v;
            g[u].push_back(edge(v, w));
        }
    }
    ll dijkstra(int s, int t) { // 最短路
        vector<ll> dis(n, INF);
        vector<bool> vis(n, false);
        dis[s] = 0;
        priority_queue<edge> pq;
        pq.push(edge(s, 0));
        while (!pq.empty()) {
            auto top = pq.top();
            pq.pop();
            if (!vis[top.to]) {
                vis[top.to] = true;
                for (auto nxt : g[top.to]) {
                    if (!vis[nxt.to] && dis[nxt.to] > nxt.val + dis[top.to]) {
                        dis[nxt.to] = nxt.val + dis[top.to];
                        pq.push(edge(nxt.to, dis[nxt.to]));
                    }
                }
            }
        }
        return dis[t];
    }
#undef ll
}

最小生成树

struct edge {
    int u, v;
    int w;
    edge(int u_ = 0, int v_ = 0, int w_ = 0) :u(u_), v(v_), w(w_) {}
    bool operator< (const edge& k) const {
        return w < k.w;
    }
};
long long kruskal() { // kruskal算法 需要并查集
    int n, m;
    cin >> n >> m;
    dsu u(n);
    vector<edge> g(m);
    for (auto& i : g) {
        cin >> i.u >> i.v >> i.w;
        --i.u, --i.v;
    }
    sort(g.begin(), g.end());
    long long res = 0;
    for (auto i : g) {
        if (u.unite(i.u, i.v)) {
            res += i.w;
        }
    }
    return res;
}

最小树形图

namespace ZL {
    // a 尽量开大, 之后的边都塞在这个里面
    const int N = 100010, M = 100010, inf = 1e9;
    struct edge {
        int u, v, w, use, id;
        edge(int u_ = 0, int v_ = 0, int w_ = 0, int use_ = 0, int id_ = 0)
            : u(u_), v(v_), w(w_), use(use_), id(id_) {}
    }b[M], a[2000100];
    int n, m, ans, pre[N], id[N], vis[N], root, In[N], h[N], len, way[M];
    // 从root 出发能到达每一个点的最小树形图
    // 先调用init 然后把边add 进去, 需要方案就getway, way[i] 为1 表示使用
    // 最小值保存在ans
    void init(int _n, int _root) { // 点数 根节点
        n = _n; m = 0; b[0].w = inf; root = _root;
    }
    void add(int u, int v, int w) {
        m++;
        b[m] = edge(u, v, w, 0, m);
        a[m] = b[m];
    }
    int work() {
        len = m;
        for (;;) {
            for (int i = 1; i <= n; i++) { pre[i] = 0; In[i] = inf; id[i] = 0; vis[i] = 0; h[i] = 0; }
            for (int i = 1; i <= m; i++) {
                if (b[i].u != b[i].v && b[i].w < In[b[i].v]) {
                    pre[b[i].v] = b[i].u; In[b[i].v] = b[i].w; h[b[i].v] = b[i].id;
                }
            }
            for (int i = 1; i <= n; i++) if (pre[i] == 0 && i != root) return 0;
            int cnt = 0; In[root] = 0;
            for (int i = 1; i <= n; i++) {
                if (i != root) a[h[i]].use++; int now = i; ans += In[i];
                while (vis[now] == 0 && now != root) { vis[now] = i; now = pre[now]; }
                if (now != root && vis[now] == i) {
                    cnt++; int kk = now;
                    while (1) {
                        id[now] = cnt; now = pre[now];
                        if (now == kk) break;
                    }
                }
            }
            if (cnt == 0) return 1;
            for (int i = 1; i <= n; i++) if (id[i] == 0) id[i] = ++cnt;
            // 缩环, 每一条接入的边都会茶包原来接入的那条边, 所以要调整边权
            // 新加的边是u, 茶包的边是v
            for (int i = 1; i <= m; i++) {
                int k1 = In[b[i].v], k2 = b[i].v;
                b[i].u = id[b[i].u];
                b[i].v = id[b[i].v];
                if (b[i].u != b[i].v) {
                    b[i].w -= k1; a[++len].u = b[i].id; a[len].v = h[k2]; b[i].id = len;
                }
            }
            n = cnt; root = id[root];
        }
        return 1;
    }
    void getway() {
        for (int i = 1; i <= m; i++) way[i] = 0;
        for (int i = len; i > m; i--) { a[a[i].u].use += a[i].use; a[a[i].v].use -= a[i].use; }
        for (int i = 1; i <= m; i++) way[i] = a[i].use;
    }
}

最近公共祖先/倍增LCA

constexpr int SIZE = 200010;
constexpr int DEPTH = 21; // 最大深度 2^DEPTH - 1
int pa[SIZE][DEPTH], dep[SIZE];
vector<int> g[SIZE]; //邻接表
void dfs(int rt, int fin) { //预处理深度和祖先
    pa[rt][0] = fin;
    dep[rt] = dep[pa[rt][0]] + 1; //深度
    for (int i = 1; i < DEPTH; ++i) { // rt 的 2^i 祖先等价于 rt 的 2^(i-1) 祖先 的 2^(i-1) 祖先
        pa[rt][i] = pa[pa[rt][i - 1]][i - 1];
    }
    int sz = g[rt].size();
    for (int i = 0; i < sz; ++i) {
        if (g[rt][i] == fin) continue;
        dfs(g[rt][i], rt);
    }
}

int LCA(int x, int y) {
    if (dep[x] > dep[y]) swap(x, y);
    int dif = dep[y] - dep[x];
    for (int j = 0; dif; ++j, dif >>= 1) {
        if (dif & 1) {
            y = pa[y][j]; //先跳到同一高度
        }
    }
    if (y == x) return x;
    for (int j = DEPTH - 1; j >= 0 && y != x; --j) { //从底往上跳
        if (pa[x][j] != pa[y][j]) { //如果当前祖先不相等 我们就需要更新
            x = pa[x][j];
            y = pa[y][j];
        }
    }
    return pa[x][0];
}

二分图最大权匹配/KM

namespace KM {
    typedef long long ll;
    const int maxn = 510;
    const int inf = 1e9;
    int vx[maxn], vy[maxn], lx[maxn], ly[maxn], slack[maxn];
    int w[maxn][maxn]; // 以上为权值类型
    int pre[maxn], left[maxn], right[maxn], NL, NR, N;
    void match(int& u) {
        for (; u; std::swap(u, right[pre[u]]))
            left[u] = pre[u];
    }
    void bfs(int u) {
        static int q[maxn], front, rear;
        front = 0; vx[q[rear = 1] = u] = true;
        while (true) {
            while (front < rear) {
                int u = q[++front];
                for (int v = 1; v <= N; ++v) {
                    int tmp;
                    if (vy[v] || (tmp = lx[u] + ly[v] - w[u][v]) > slack[v])
                        continue;
                    pre[v] = u;
                    if (!tmp) {
                        if (!left[v]) return match(v);
                        vy[v] = vx[q[++rear] = left[v]] = true;
                    } else slack[v] = tmp;
                }
            }
            int a = inf;
            for (int i = 1; i <= N; ++i)
                if (!vy[i] && a > slack[i]) a = slack[u = i];
            for (int i = 1; i <= N; ++i) {
                if (vx[i]) lx[i] -= a;
                if (vy[i]) ly[i] += a;
                else slack[i] -= a;
            }
            if (!left[u]) return match(u);
            vy[u] = vx[q[++rear] = left[u]] = true;
 
        }
 
    }
    void exec() {
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= N; ++j) {
                slack[j] = inf;
                vx[j] = vy[j] = false;
            }
            bfs(i);
        }
    }
    ll work(int nl, int nr) { // NL , NR 为左右点数, 返回最大权匹配的权值和
        NL = nl; NR = nr;
        N = std::max(NL, NR);
        for (int u = 1; u <= N; ++u)
            for (int v = 1; v <= N; ++v)
                lx[u] = std::max(lx[u], w[u][v]);
        exec();
        ll ans = 0;
        for (int i = 1; i <= N; ++i)
            ans += lx[i] + ly[i];
        return ans;
    }
    void output() { // 输出左边点与右边哪个点匹配, 没有匹配输出0
        for (int i = 1; i <= NL; ++i)
            printf("%d ", (w[i][right[i]] ? right[i] : 0));
        printf("\n");
    }
}

2-sat

/*
 * 2-sat
 * n 表示限制个数, i 为取(真), i+n 为不取(假)
 * 先初始化再建图
 * 建图时 若输入两组或关系 ((x = a) || (y = b))则有:
   if (a && b) {
       Twosat::add(x + n, y), Twosat::add(y + n, x);
   } else if (!a && b) {
       Twosat::add(x, y), Twosat::add(y + n, x + n);
   } else if (a && !b) {
       Twosat::add(x + n, y + n), Twosat::add(y, x);
   } else {
       Twosat::add(x, y + n), Twosat::add(y, x + n);
   }
 * 表示若条件1/2不成立 则条件2/1必定成立
 * 构造结果保存在 ans 数组 若ans[i] = 0则取i 否则取i+n
 * */
namespace Twosat {
    const int M = 4000010, N = 1000010;
    struct edge {
        int next, point;
        edge(int n_ = 0, int p_ = 0) :next(n_), point(p_) {}
    } e[M];
    int n, len, now, sign, head, sign2;
    int p[N << 1], dfs[N << 1], low[N << 1], where[N << 1];
    int s[N << 1], in[N << 1], ans[N], out[N << 1];
    void add(int k1, int k2) {
        e[++len] = edge(p[k1], k2);
        p[k1] = len;
    }
    void init(int _n) {
        n = _n;
        for (int i = 0; i <= n * 2 + 1; i++) {
            p[i] = where[i] = dfs[i] = low[i] = s[i] = in[i] = out[i] = 0;
        }
        len = now = sign = head = sign2 = 0;
    }
    void tarjan(int k1) {
        s[++head] = k1; in[k1] = 1; dfs[k1] = ++sign; low[k1] = sign;
        for (int i = p[k1]; i; i = e[i].next) {
            int j = e[i].point;
            if (dfs[j] == 0) {
                tarjan(j);
                low[k1] = min(low[k1], low[j]);
            } else if (in[j]) {
                low[k1] = min(low[k1], dfs[j]);
            }
        }
        if (dfs[k1] == low[k1]) {
            now++;
            while (1) {
                where[s[head]] = now;
                in[s[head]] = 0;
                out[s[head]] = ++sign2;
                head--;
                if (s[head + 1] == k1) {
                    break;
                }
            }
        }
    }
    int solve() { // ans[i] = 0 表示取i, 否则表示取i+n
        for (int i = 1; i <= n * 2; i++) {
            if (dfs[i] == 0) {
                tarjan(i);
            }
        }
        for (int i = 1; i <= n; i++) {
            if (where[i] == where[i + n]) {
                return 0;
            }
        }
        for (int i = 1; i <= n; i++) {
            if (out[i] < out[i + n]) {
                ans[i] = 0;
            } else {
                ans[i] = 1;
            }
        }
        return 1;
    }
}

最大团/Bron-Kerbosch

/*
 * 最大团 Bron-Kerbosch algorithm
 * 最劣复杂度 O(3^(n/3))
 * 采用位运算形式实现
 * */
namespace Max_clique {
#define ll long long
#define TWOL(x) (1ll<<(x))
    const int N = 60;
    int n, m;      // 点数 边数
    int r = 0;     // 最大团大小
    ll G[N];       // 以二进制形式存图
    ll clique = 0; // 最大团 以二进制形式存储
    void BronK(int S, ll P, ll X, ll R) { // 调用时参数这样设置: 0, TWOL(n)-1, 0, 0
        if (P == 0 && X == 0) {
            if (r < S) {
                r = S;
                clique = R;
            }
        }
        if (P == 0) return;
        int u = __builtin_ctzll(P | X);
        ll c = P & ~G[u];
        while (c) {
            int v = __builtin_ctzll(c);
            ll pv = TWOL(v);
            BronK(S + 1, P & G[v], X & G[v], R | pv);
            P ^= pv; X |= pv; c ^= pv;
        }
    }
    void init() {
        cin >> n >> m;
        for (int i = 0; i < m; i++) {
            int u, v;
            cin >> u >> v;
            --u, --v;
            G[u] |= TWOL(v);
            G[v] |= TWOL(u);
        }
        BronK(0, TWOL(n)-1, 0, 0);
        cout << r << ' ' << clique << '\n';
    }
}

上一篇:10.29测试


下一篇:725. 分隔链表-链表的分割