题目
题目描述
给出一个有向完全图,其中
a
→
b
(
a
≠
b
)
a\rightarrow b\;(a\ne b)
a→b(a=b) 的边权为
v
a
v_a
va 。
接下来会进行 m m m 次修改,每次修改会改变一条边的边权。注意是修改,而不是新加一条边。
所有修改结束后,请求出 ∑ i ≠ j d i s ( i , j ) \sum_{i\ne j}dis(i,j) ∑i=jdis(i,j),其中 d i s ( a , b ) dis(a,b) dis(a,b) 表示 a a a 到 b b b 的最短路长度。
数据范围与提示
n
⩽
1
0
5
n\leqslant 10^5
n⩽105 但
m
⩽
3
×
1
0
3
m\leqslant 3\times 10^3
m⩽3×103 。
思路
感觉这题比上一道题更好想啊,为什么没有人 A C AC AC 呢?哦,原来是 O U Y E \sf OUYE OUYE 在装弱!
首先我们可能会联想起一道很古老很经典的题目:
给出一个无向连通图 G = ( V , E ) G=(V,E) G=(V,E) 满足 ∣ E ∣ − 10 ⩽ ∣ V ∣ ⩽ 1 0 5 |E|-10\leqslant|V|\leqslant 10^5 ∣E∣−10⩽∣V∣⩽105,询问 q ( q ⩽ 1 0 5 ) q\;(q\leqslant 10^5) q(q⩽105) 次 a , b a,b a,b 间最短路。
这道经典题的做法是,考虑树边和非树边交替,那么树边的部分是走树上的唯一路径。于是只需要存 2 ( ∣ E ∣ − ∣ V ∣ + 1 ) = 22 2(|E|-|V|+1)=22 2(∣E∣−∣V∣+1)=22 个点(非树边的端点)的最短路值,将树边部分都抽象为一条边。每次询问就是一次 22 22 22 个点的图求最短路的过程。
于是这道题也可以考虑普通边(点权为边权)和特殊边(修改过的边)交替的路径。一个很显著的特点是,普通边是几乎构成完全图的,所以走过一条普通边 a → b a\rightarrow b a→b 后,如果还需要经过别的边才最终走到 c c c,一定是因为 a → c a\rightarrow c a→c 是特殊边——否则 a → c a\rightarrow c a→c 一步就到,路径长度更小。
如果上面的条件成立,那么 a → c a\rightarrow c a→c 是永远用不到的。就把这样的边的边权直接改成 a → b → ⋯ → c a\rightarrow b\rightarrow\cdots\rightarrow c a→b→⋯→c 的最小权,这样就可以保证走过普通边之后不走特殊边了!
很不幸的是, a → b → ⋯ → c a\rightarrow b\rightarrow\cdots\rightarrow c a→b→⋯→c 本质就是 a a a 到 c c c 的最短路径。如果我们能求出最短路径了,题目就已经解决了 😂
重新想想上面那道题给我们的启发。我们只能 简化问题至较小规模,而不是直接求出答案。上文说明, a → c a\rightarrow c a→c 是特殊边时,需要特殊计算距离。于是我们可以 把特殊边(视为无向边时)构成的连通块 拿出来,单独做最短路。显然离开连通块时,需要走过普通边,并且不存在上面的特殊情况了,所以普通边一定是最后一步。
选择哪个普通边呢?其实就是 d i s ( x ) + a ( x ) dis(x)+a(x) dis(x)+a(x) 最小的那一个呗。仅剩的问题是解决连通块内部的最短路问题。
首先考虑是否会走到连通块外部,再走回来?其实是可以的。这种情况肯定取连通块外部的 min a \min a mina,不要忘记这种情况!
然后是内部连边。直接线段树优化建图会劣一点。用线段树优化 d i j k s t r a \rm dijkstra dijkstra 的松弛操作,或者用这道题里面的那种 t a g tag tag 和 d i s dis dis 两个优先队列的思想,都可以做到 O ( m log m ) \mathcal O(m\log m) O(mlogm) 求出单源最短路。枚举起点就是 O ( m 2 log m ) \mathcal O(m^2\log m) O(m2logm) 了。
代码
#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int x){
if(x > 9) writeint(x/10);
putchar(char((x%10)^48));
}
const int INFTY = 0x3fffffff;
const int MAXM = 3005;
int lost; ///< length of segment tree
namespace SgTree{
int val[MAXM<<2], tag[MAXM<<2];
void fuckNode(int o,int v){
if(val[o] == INFTY) return ; // dead
if(val[o] > v) val[o] = tag[o] = v;
else if(tag[o] > v) tag[o] = v;
}
void pushDown(int o){
fuckNode(o<<1,tag[o]);
fuckNode(o<<1|1,tag[o]);
tag[o] = INFTY;
}
void pushUp(int o){
val[o] = min(val[o<<1],val[o<<1|1]);
}
# define __ROOT int o=1,int l=1,int r=lost
# define LSON o<<1,l,(l+r)>>1
# define RSON o<<1|1,((l+r)>>1)+1,r
void build(const int &_v=INFTY,__ROOT){
val[o] = tag[o] = _v;
if(l != r) build(_v,LSON), build(_v,RSON);
}
void modify(int ql,int qr,int qv,__ROOT){
if(qr < l || r < ql) return ;
if(ql <= l && r <= qr)
return fuckNode(o,qv);
pushDown(o); modify(ql,qr,qv,LSON);
modify(ql,qr,qv,RSON); pushUp(o);
}
void pop_top(int &qid,int &qv,__ROOT){
if(l == r){
qv = val[o], qid = l;
val[o] = tag[o] = INFTY;
return ;
}
if(pushDown(o), val[o<<1] == val[o])
pop_top(qid,qv,LSON);
else pop_top(qid,qv,RSON);
pushUp(o); // after erasing
}
}
const int MAXN = 100005;
struct Edge{
int to, nxt, val;
Edge() = default;
Edge(int _to,int _nxt,int _val):
to(_to),nxt(_nxt),val(_val){ }
};
Edge e[MAXM<<1];
int head[MAXN], cntEdge;
void addEdge(int a,int b,int c){
e[cntEdge] = Edge(b,head[a],c);
head[a] = cntEdge ++;
}
bool vis[MAXN];
int sta[MAXN], top;
void collect(int x){
vis[x] = true, sta[++ top] = x;
for(int i=head[x]; ~i; i=e[i].nxt)
if(!vis[e[i].to]) collect(e[i].to);
}
int outer[MAXN], tmp[MAXM], tmpsiz;
int ys[MAXN]; ///< re-index
long long ans;
void dijkstra(int n,const int &coe,const int &min_a){
lost = n; // only n nodes
for(int qd=1; qd<=n; ++qd){
SgTree::build(outer[sta[qd]]+min_a);
SgTree::modify(qd,qd,0);
int sy_v = INFTY;
for(int cs=n; cs; --cs){
int x, v; SgTree::pop_top(x,v);
ans += v; tmpsiz = 0;
if(sy_v > v+outer[sta[x]])
sy_v = v+outer[sta[x]];
for(int i=head[sta[x]]; ~i; i=e[i].nxt)
if(!(i&1)){ // real edge
tmp[++ tmpsiz] = ys[e[i].to];
SgTree::modify(ys[e[i].to],
ys[e[i].to],v+e[i].val);
}
sort(tmp+1,tmp+tmpsiz+1);
tmp[++ tmpsiz] = n+1;
for(int i=1; i<=tmpsiz; ++i)
if(tmp[i-1]+1 != tmp[i])
SgTree::modify(tmp[i-1]+1,
tmp[i]-1,v+outer[sta[x]]);
}
ans += llong(sy_v)*coe;
}
}
int id[MAXN];
bool cmp(const int &x,const int &y){
return outer[x] < outer[y];
}
int main(){
int n = readint(), m = readint();
rep(i,1,n) outer[id[i] = i] = readint();
std::sort(id+1,id+n+1,cmp);
memset(head+1,-1,n<<2);
for(int a,b,c; m; --m){
a = readint(), b = readint(), c = readint();
addEdge(a,b,c); addEdge(b,a,c);
}
id[n+1] = n+1, outer[n+1] = INFTY>>1;
for(int i=1,p; i<=n; ++i)
if(vis[i] == false){
top = 0, collect(i);
rep(i,1,top) ys[sta[i]] = i;
for(p=1; ys[id[p]]; ++p);
dijkstra(top,n-top,outer[id[p]]);
rep(i,1,top) ys[sta[i]] = 0; // clear
}
printf("%lld\n",ans);
return 0;
}

该博客讨论了一种有向完全图中,针对边权修改的最短路问题的解决方案。作者提到,可以将普通边和特殊边(修改过的边)交替考虑,通过构建树状结构简化问题,利用线段树优化Dijkstra算法或使用优先队列实现O(m log m)的时间复杂度求解单源最短路。文章还给出了具体的代码实现,涉及到了树形结构、最短路算法和数据结构的优化技巧。
17

被折叠的 条评论
为什么被折叠?



