[JOISC 2018 Day 4] 野猪
题面
link
给定一个包含 \(n \le 2000\) 个结点,\(m\le 3000\) 条带权无向连通图。
开始时有一个长度为 \(k \le 10^5\) 的序列 \(x_1, x_2 . . . x_k\),表示 JOI 君开始时在\(x_1\), 它要依次访问结点 \(x_2 . . . x_k\) 但是,不能沿原路返回前一个到达的结点。序列中可能有重复结点, 但保证序列中相邻两结点不同。
接下来有 \(t ≤ 10^5\) 次修改, 每次修改会给出两个整数 \(P_k,Q_k\), 表示将 \(x_{P_k}\) 修改为 \(Q_k\)。每次修改后,求满足要求的最短路径。
题解
先考虑没有不能沿原路返回怎么做?发现只需要求出任意两点之间的最短路 \(dis_{i,j}\) 就行。
再考虑一下,原路返回只会在什么情况下出现?从\(x_{i-1}\rightarrow x_i\),现在要到\(x_{i+1}\),但过去的最短路和过来的时候冲突了。怎么办?
那么我们对一条路径\(x\rightarrow y\),考虑记录起点后的第一个点 \(s\),终点前的第一个点 \(t\),如果\(t_i\neq s_{i+1}\),这两条路径就是合法的,但是总会有不合法的情况,怎么办?
肯定还需要记录不同的走法,但是如果起点相同,终点也相同,那么这条路径肯定不会优于最短路。那么我们就记录起点终点不同的,设\(s_i,t_i\)分别表示对应路径中起点后的第一个点和终点前的第一个点,我们记录这四条路径:
- 最短路
- 所有\(s_2\neq s_1,t_2\neq t_1\)中的最短路
- 所有\(s_3\neq s_1,t_3\neq t_2\)中的最短路
- 所有\(s_4\neq s_2,t_4\neq t_1\)中的最短路
为什么就是这 \(4\) 条路径?因为这4条边包含了所有可能碰到的冲突情况,比如若最短路的 \(s\) 冲突,那么有第 \(2,3\) 条路径,而若 \(2\) 的 \(t\) 冲突则有第 \(3\) 条路径,而且这些路径都是在最优不满足的情况下的次优选择。
之后线段树就可以维护修改了,pushup
的时候可以暴力枚举左边和右边的路径两两组合,之后依次枚举选出这四条路径。
最后就只剩如何对每对点\(x,y\),求出\(x\rightarrow y\)路径的这四条路径。发现这个时候我们用点表示最短路(即记录从 \(S\) 到点 \(x\) 的最短路)无法求出其他三条路径,怎么办?
用点不行,那么我们就用边表示,因为边刚好包含了点的转移,我们枚举第一步走的哪一条边,就确定了起点和\(s\),最后也可以知道终点和\(t\)。
但是这样我们的最短路就要改一下,如果直接像TAX那题一样,边化点,点化边,再优化连边会很麻烦。这里我们可以简单一点。考虑直接跑dij,只是最后用边记录答案,那么有哪些边最后没有记录答案?其实就是顺着最短路路径的反向边,这也是用点表示最短路不能展现的贡献方案。
那么我们可以记录到达点 \(x\) 的边 \(v_x\) ,那么我们再一次遍历到点 \(x\) 的时候就可以顺着 \(v_x\) 返回前面的点,从而更新所有的边。代码:
ll d[maxn << 1];
int v[maxn];
priority_queue<pair<ll, int>> p;
void zuidl(int S) {
memset(v, 0, sizeof(v));
memset(d, 0x3f, sizeof(d));
d[S] = a[S].z;
p.push(mp(-d[S], S));
while (p.size()) {
int id = p.top().se;
ll D = -p.top().fi;
p.pop();
int x = a[id].y;
if (D > d[id] || v[x] < 0)continue;
if (!v[x]) {
v[x] = id;//记录到达x的点的编号
for (int i = h[x]; i; i = a[i].ne) {
if (i == (id ^ 1) )continue;//不能通过上一个边的反边
if (d[i] > d[id] + a[i].z) {
d[i] = d[id] + a[i].z;
p.push(mp(-d[i], i));
}
}
} else {
int y = v[x] ^ 1;// 反向边
v[x] = -1;
if (d[y] > d[id] + a[y].z) {
d[y] = d[id] + a[y].z;
p.push(mp(-d[y], y));
}
}
}
}
启发
- 记录路径这里背吧...可以用来处理满足\(b_{i-1}\neq a_i\)条件下的最值问题。
- 需要得到一条路径起点的下一个点,终点的前一个点,而且还需要多条路径,就按上面的最短路方法。