在最短路、强连通分量、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});
}
}
}
}
}

