线段树优化建边的速成
前言
这个东西比较简单易懂。
正文
问题引入 \Rightarrow CF786B (PLUS)
现在有一个
- 给出三个数
u,v,w ,表示连接一条有向边(u,v) ,权值为w 。 - 给出四个数
u, l, r, w ,表示从u 向[l,r] 中所有的点分别连一条有向边,权值都是w 。 - 依然给出四个数
u,l,r,w ,表示[l,r] 中所有的点分别向u 连一条有向边,权值都是w 。 - (原题没有)给出五个数
l_1, r_1, l_2, r_2, w ,表示[l_1,r_1] 中所有的点分别向[l_2,r_2] 中所有的点连一条有向边。
我们需要求出这个图的单源最短路。
基本算法讲解
暴力建图是不可能的。
初始化
将每个点分为入点和出点,一个点
比如
建图
现在我们需要想办法将原图转化,尽量减少边的数目。
对于出树中某个节点,其表示的区间为
-
单点
\mathrm{to} 单点:对于一条边(u,v) ,可以直接转化为另一条边(u_{out}, v_{in}) 。 -
单点
\mathrm{to} 区间:设单点为u , 区间为[l,r] 。众所周知,一个区间在线段树中会被分成不多于\log_2 (r-l+1) 个子区间,对于每一个分出的子区间[l',r'] ,都连接一条从u_{out} 到这个子区间在入树中的对应节点的有向边。 比如u=4,[l,r] = [1,3] 的时候,可以这样连边: -
区间
\mathrm{to} 单点:和 单点\mathrm{to} 区间 类似,不说了。 -
区间
\mathrm{to} 区间:设两个区间为[l_1, r_1],[l_2, r_2] ,边的权值为w ,应用上面的方法,我们可以对于两个区间的所有子区间分别两两连边,但是这样的边的数量是O(\log_2^2 n) 的。正确的姿势是构建一个虚点p ,用 区间\mathrm{to} 单点 的方法,连接[l_1, r_1] 到p ,这些边的权值是w ,接着用 单点\mathrm{to} 区间 ,连接p 到[l_2, r_2] ,边权都是0 。
尾声
接下来只需要在新图上跑一次单源最短路就好啦,此时源点是原图源点的出点。
Code
PS:这是原题的代码,不是加强之后的代码
const int maxn = 1e5 + 5, maxlg2n = 20;
int n;
struct Edge {
int v, nex; LL w;
Edge(int v = 0, LL w = 0, int nex = 0) : v(v), w(w), nex(nex) {}
} E[maxn * maxlg2n << 2];
int hd[maxn << 3], tote;
void addedge(int u, int v, LL w) {
E[++tote] = Edge(v, w, hd[u]), hd[u] = tote;
}
struct STNode {
int ls, rs, l, r;
} nd[maxn << 3];
int inid[maxn], outid[maxn], itr_rt, otr_rt, nd_cnt;
void build_tr(int& o, int l, int r, bool fl) {
o = ++nd_cnt, nd[o].l = l, nd[o].r = r;
if (l == r) {
if (fl) inid[l] = o, addedge(o, outid[l], 0), addedge(outid[l], o, 0);
else outid[l] = o;
return ;
}
int mid = (l + r) >> 1;
build_tr(nd[o].ls, l, mid, fl);
build_tr(nd[o].rs, mid + 1, r, fl);
if (fl) addedge(o, nd[o].ls, 0), addedge(o, nd[o].rs, 0);
else addedge(nd[o].ls, o, 0), addedge(nd[o].rs, o, 0);
}
void addedge_sig_to_sig(int u, int v, LL w) {
addedge(outid[u], inid[v], w);
}
void addedge_seq_to_sig(int p, int l, int r, int u, LL w) {
if (r < nd[p].l || l > nd[p].r) return ;
if (l <= nd[p].l && nd[p].r <= r) {
addedge(p, inid[u], w);
return ;
}
addedge_seq_to_sig(nd[p].ls, l, r, u, w);
addedge_seq_to_sig(nd[p].rs, l, r, u, w);
}
void addedge_sig_to_seq(int p, int u, int l, int r, LL w) {
if (r < nd[p].l || l > nd[p].r) return ;
if (l <= nd[p].l && nd[p].r <= r) {
addedge(outid[u], p, w);
return ;
}
addedge_sig_to_seq(nd[p].ls, u, l, r, w);
addedge_sig_to_seq(nd[p].rs, u, l, r, w);
}
LL dis[maxn << 3]; int vis[maxn << 3];
priority_queue< pair<LL, int> > pq;
void dij(int src) {
for (int i = 1; i <= nd_cnt; i++) dis[i] = -1;
dis[src] = 0, pq.push(make_pair(0, src));
while (!pq.empty()) {
int u = pq.top().second; pq.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = hd[u]; i; i = E[i].nex) {
int v = E[i].v;
if (dis[v] == -1 || dis[v] > dis[u] + E[i].w)
dis[v] = dis[u] + E[i].w,
pq.push(make_pair(-dis[v], v));
}
}
}
int main() {
int q, s;
scanf("%d%d%d", &n, &q, &s);
build_tr(otr_rt, 1, n, false), build_tr(itr_rt, 1, n, true);
for (int i = 1; i <= q; i++) {
int t, u, v1, v2; LL w;
scanf("%d", &t);
if (t == 1) {
scanf("%d%d%lld", &u, &v1, &w);
addedge_sig_to_sig(u, v1, w);
} else if (t == 2) {
scanf("%d%d%d%lld", &u, &v1, &v2, &w);
addedge_sig_to_seq(itr_rt, u, v1, v2, w);
} else {
scanf("%d%d%d%lld", &u, &v1, &v2, &w);
addedge_seq_to_sig(otr_rt, v1, v2, u, w);
}
}
dij(outid[s]);
for (int i = 1; i <= n; i++) printf("%lld ", dis[inid[i]]);
return 0;
}