ACM / 图论 / 数据结构 · 2024-03-09

线段树优化建图

在最短路、强连通分量、2-SAT、网络流等图论问题中,边数有时达到 O(n2) 甚至 O(n3),成为时间或空间复杂度的瓶颈所在,使用优化建图可以在不影响效果的情况下建出边数更少的图。

支持以下四种操作:

u->v连边, u->[l, r]连边, [l, r]->v 连边, [l1, r1]->[l2, r2]连边

  • 普通版本:

区间操作想到线段树,可以把区间拆成 nlogn 个线段树上区间。先按照线段树建出两棵树:外向树和内向树。对于所有连入一个区间的操作,将边连向外向树,这代表着连入一个大区间的边在经过外向树边后,也可以连入小区间;对于所有从一个区间连出的操作,将边从内向树连出,这代表着一个小区间在经过外向树的边后,可以连出大区间的边。

为了避免冲突,将外向树的编号从 1 开始,内向树的编号从 4n 开始,单个节点的编号从 8n 开始,且要与两棵树的叶子相连。

区间与区间相连按照上面做法是 logn * logn 条边,可以新建两个节点以一条边相连,将边权设置在这条边上。内向树对应区间全部由其中一个节点连入,外向树对应区间全部连到另一个节点,这样就只有 logn 条边。总点数大概在 10n 左右,总边数是 mlogn 级别。

  • LinXce版本:

目前只有 u->v连边, u->[l, r]连边, [l, r]->v 连边,三种连边方式。

区间操作联系线段树,以线段树的编号作为节点编号。模仿普通线段树区间查询操作,若当前区间在目标区间 [l, r] 内,就连接点和当前区间,然后向下访问。

进行最短路运算时,对于当前节点 i,向上查找包含它的父区间,向下查找它的所有子区间,将所有查找到的节点以及其本身加入栈,并 vis 标记。计算路径权值时,以当前节点的 dis[i] 为初始代价,遍历所有栈中的节点 j,以栈中结点边 (j, k) 的权值 w ,用 dis[i] + w 更新到达的节点的 dis[k]。

typedef pair<ll, int> pli;
struct edge { ll v, w; };
stack<int> st;
vector<edge> g[N << 2];
ll dis[N << 2];
int node[N], leaf[N << 2];
bool vis[N << 2];
void build(int k, int l, int r)
{
    if (l == r)
    {
        node[l] = k;
        leaf[k] = l; //记录叶子节点对应位置
        return;
    }
    int mid = l + r >> 1;
    build(k << 1, l, mid);
    build(k << 1 | 1, mid + 1, r);
}
//连接边:op == 0由点单向连接边,op == 1由边单向连接点
void conPI(int k, int l, int r, int x, int y, int u, ll w, int op)
{
    if (x <= l && r <= y)
        return op ? g[k].pb({node[u], w}) : g[node[u]].pb({k, w});
    int mid = l + r >> 1;
    if (x <= mid)
        conPI(k << 1, l, mid, x, y, u, w, op);
    if (mid < y)
        conPI(k << 1 | 1, mid + 1, r, x, y, u, w, op);
}
void upRange(int k, ll w) //向上搜索边
{
    if (!vis[k])
        st.push(k), vis[k] = true;
    if (k == 1)
        return;
    upRange(k >> 1, w);
}
void downRange(int k, ll w) //向下搜索边
{
    dis[k] = min(dis[k], dis[k >> 1]);
    if (!vis[k])
        st.push(k), vis[k] = true;
    if (leaf[k])
        return;
    downRange(k << 1, w);
    downRange(k << 1 | 1, w);
}
void dijkstra(int s, int n)
{
    memset(dis, 0x3f, sizeof dis);
    dis[node[s]] = 0;
    priority_queue<pli, vector<pli>, greater<pli>> pq;
    pq.push({0, node[s]});
    while (!pq.empty())
    {
        ll w = pq.top().fi, u = pq.top().se;
        pq.pop();
        upRange(u, w);
        downRange(u, w);
        while (!st.empty())
        {
            int e = st.top();
            st.pop();
            for (auto ed : g[e])
            {
                ll v = ed.v, w = ed.w;
                if (dis[v] > dis[u] + w)
                {
                    dis[v] = dis[u] + w;
                    pq.push({dis[v], v});
                }
            }
        }
    }
}