BZOJ 3514 [LCT][主席树]

本文介绍了一种解决特定在线图论问题的方法,通过维护最大生成树并使用主席树来快速查询不同时间段内的连通块数量,实现了对图中边进行操作后的连通性分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DescriptionDescription

NN个点M条边的无向图,询问保留图中编号在[l,r][l,r]的边的时候图中的联通块个数。
强制在线。

SolutionSolution

考虑一个森林的连通图个数。
就是点数减边数。
按加入边的时间为边的权值,维护最大生成树。
加入一条边时,若已经联通,就CutCut掉权值最小的边。
记录一下每条边的prepre值:表示加入它时CutCut掉的边的权值。
若不连通则prepre00
这样对于一个区间[l,r]中的边来说,只有prepre值小于ll才会是树边。
这个东西用主席树维护一下就好啦。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;

const int N = 404040;
const int INF = 1 << 30;
typedef pair<int, int> P;

inline char get(void) {
    static char buf[100000], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, 100000, stdin);
        if (S == T) return EOF;
    }
    return *S++;
}
inline void read(int &x) {
    static char c; x = 0;
    for (c = get(); c < '0' || c > '9'; c = get());
    for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0';
}

inline int Min(int a, int b) {
    return a < b ? a : b;
}

int n, m, k, x, y, p, ans;
P G[N];
int pre[N];
namespace UFS {
    int fa[N], rk[N];
    void Init(int n) {
        for (int i = 1; i <= n; i++) fa[i] = i;
    }
    inline int Fa(int x) {
        return fa[x] == x ? x : Fa(fa[x]);
    }
    inline int Merge(int x, int y) {
        static int f1, f2;
        f1 = Fa(x); f2 = Fa(y);
        if (f1 == f2) return 0;
        if (rk[f1] < rk[f2]) swap(f1, f2);
        if (rk[f1] == rk[f2]) rk[f1]++;
        fa[f2] = f1; return 1;
    }
}
namespace Seg {
    int rt[N];
    int Tcnt;
    int ls[N * 40], rs[N * 40], sum[N * 40];
    inline void Modify(int &o, int l, int r, int pos, int x) {
        ls[++Tcnt] = ls[o]; rs[Tcnt] = rs[o];
        sum[Tcnt] = sum[o]; o = Tcnt;
        if (l == r) return (void)(++sum[o]);
        int mid = (l + r) >> 1;
        if (pos <= mid) Modify(ls[o], l, mid, pos, x);
        else Modify(rs[o], mid + 1, r, pos, x);
        sum[o] = sum[ls[o]] + sum[rs[o]];
    }
    inline int Sum(int o, int l, int r, int L, int R) {
        if (!o) return 0;
        if (l >= L && r <= R) return sum[o];
        int mid = (l + r) >> 1, res = 0;
        if (L <= mid) res += Sum(ls[o], l, mid, L, R);
        if (R > mid) res += Sum(rs[o], mid + 1, r, L, R);
        return res;
    }
    inline void Add(int i, int res, int x) {
        Modify(rt[i], 1, n + m, res, x);
    }
    inline int Query(int l, int r, int L, int R) {
        return Sum(rt[r], 1, n + m, L, R) - Sum(rt[l - 1], 1, n + m, L, R);
    }
}
namespace LCT {
    struct node {
        node *ch[2];
        node *fa;
        int mn, id, rev;
        inline void PushUp(void) {
            mn = Min(ch[0]->mn, ch[1]->mn);
            mn = Min(id, mn);
        }
        inline void PushDown(void) {
            if (rev) {
                swap(ch[0], ch[1]);
                ch[0]->rev ^= 1;
                ch[1]->rev ^= 1;
                rev = 0;
            }
        }
    };
    node T[N];
    node *null;
    inline bool IsRoot(node* x) {
        return x->fa == null || (x->fa->ch[0] != x && x->fa->ch[1] != x);
    }
    inline void Init(int n) {
        null = T;
        null->ch[0] = null->ch[1] = null->fa = null;
        null->mn = INF;
        for (int i = 1; i <= n; i++) {
            T[i].ch[0] = T[i].ch[1] = T[i].fa = null;
            T[i].id = T[i].mn = INF; T[i].rev = 0;
        }
    }
    inline void Rotate(node* x) {
        node *y = x->fa, *z = y->fa;
        int l = (y->ch[0] != x), r = l ^ 1;
        if (!IsRoot(y)) {
            if (z->ch[0] == y) z->ch[0] = x;
            else z->ch[1] = x;
        }
        x->fa = z; y->fa = x; x->ch[r]->fa = y;
        y->ch[l] = x->ch[r]; x->ch[r] = y;
        y->PushUp(); x->PushUp();
    }
    inline void Down(node* x) {
        if (!IsRoot(x)) Down(x->fa);
        x->PushDown();
    }
    inline void Splay(node* x) {
        Down(x);
        while (!IsRoot(x)) {
            node *y = x->fa, *z = y->fa;
            if (!IsRoot(y)) {
                if (y->ch[0] == x ^ z->ch[0] == y) Rotate(x);
                else Rotate(y);
            }
            Rotate(x);
        }
    }
    inline void Access(node* x) {
        for (node *y = null; x != null; x = x->fa) {
            Splay(x); x->ch[1] = y;
            x->PushUp(); y = x;
        }
    }
    inline void MakeRoot(node* x) {
        Access(x); Splay(x); x->rev ^= 1;
    }
    inline void Link(node* x, node* y) {
        MakeRoot(x); x->fa = y;
    }
    inline void Cut(node* x, node* y) {
        MakeRoot(x); Access(y); Splay(y);
        x->fa = y->ch[0] = null; y->PushUp();
    }
    inline int Query(node* x, node* y) {
        MakeRoot(x); Access(y); Splay(y);
        return y->mn;
    }
    inline void AddEdge(int x, int y, int pos) {
        Seg::rt[pos] = Seg::rt[pos - 1];
        if (x == y) return;
        G[n + pos] = P(x, y);
        T[n + pos].id = T[n + pos].mn = n + pos;
        int res = n;
        if (!UFS::Merge(x, y)) {
            res = Query(T + x, T + y);
            Cut(T + res, T + G[res].first);
            Cut(T + res, T + G[res].second);
        }
        pre[pos] = res;
        Seg::Add(pos, res, 1);
        Link(T + n + pos, T + x);
        Link(T + n + pos, T + y);
    }
}


int main(void) {
    read(n); read(m); read(k); read(p);
    LCT::Init(n + m); UFS::Init(n);
    for (int i = 1; i <= m; i++) { 
        read(x); read(y);
        LCT::AddEdge(x, y, i);
    }
    for (int i = 1; i <= k; i++) {
        read(x); read(y);
        if (p) {
            x ^= ans; y ^= ans;
        }
        printf("%d\n", ans = n - Seg::Query(x, y, n, n + x - 1));
    }
    return 0;
}
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值