[ACNOI2021]图论题

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

题目

题目描述
给出一个有向完全图,其中 a → b    ( a ≠ b ) a\rightarrow b\;(a\ne b) ab(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 n105 m ⩽ 3 × 1 0 3 m\leqslant 3\times 10^3 m3×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 E10V105,询问 q    ( q ⩽ 1 0 5 ) q\;(q\leqslant 10^5) q(q105) a , b a,b a,b 间最短路。

这道经典题的做法是,考虑树边和非树边交替,那么树边的部分是走树上的唯一路径。于是只需要存 2 ( ∣ E ∣ − ∣ V ∣ + 1 ) = 22 2(|E|-|V|+1)=22 2(EV+1)=22 个点(非树边的端点)的最短路值,将树边部分都抽象为一条边。每次询问就是一次 22 22 22 个点的图求最短路的过程。

于是这道题也可以考虑普通边(点权为边权)和特殊边(修改过的边)交替的路径。一个很显著的特点是,普通边是几乎构成完全图的,所以走过一条普通边 a → b a\rightarrow b ab 后,如果还需要经过别的边才最终走到 c c c,一定是因为 a → c a\rightarrow c ac 是特殊边——否则 a → c a\rightarrow c ac 一步就到,路径长度更小。

如果上面的条件成立,那么 a → c a\rightarrow c ac 是永远用不到的。就把这样的边的边权直接改成 a → b → ⋯ → c a\rightarrow b\rightarrow\cdots\rightarrow c abc 的最小权,这样就可以保证走过普通边之后不走特殊边了!

很不幸的是, a → b → ⋯ → c a\rightarrow b\rightarrow\cdots\rightarrow c abc 本质就是 a a a c c c 的最短路径。如果我们能求出最短路径了,题目就已经解决了 😂

重新想想上面那道题给我们的启发。我们只能 简化问题至较小规模,而不是直接求出答案。上文说明, a → c a\rightarrow c ac 是特殊边时,需要特殊计算距离。于是我们可以 把特殊边(视为无向边时)构成的连通块 拿出来,单独做最短路。显然离开连通块时,需要走过普通边,并且不存在上面的特殊情况了,所以普通边一定是最后一步。

选择哪个普通边呢?其实就是 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值