1 条题解

  • 0
    @ 2025-8-24 21:56:04

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar wsyhb
    **

    搬运于2025-08-24 21:56:04,当前版本为作者最后更新于2021-08-24 11:19:51,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    前言

    巨佬们的题解都好难懂(我是仔细阅读了

    https://www.luogu.com.cn/user/47640
    个蒟蒻打算写一篇思路清晰、尽可能严谨的题解,如有错误还请指正。

    顺带一提,这里有两组 Hack 数据。截止本文发布前(2021.8.25),本题题解区只有 FrenkiedeJong21 的题解能通过这两组数据(当然本题解的代码是可以过的),具体原因见这个帖子

    题意简述

    给定 nn 个点,mm 条边的无向图,以及起点 SS 和终点 TT。求满足下列条件的无序点对 (A,B)(A,B) 的数量。

    条件:对于任意一条从 SSTT 的最短路径,其恰好经过 AABB 其中之一

    数据范围1n,m5×1041 \le n,m \le 5 \times 10^4,边权 ww 满足 1w1091 \le w \le 10^9

    P.S. 本题数据可能出现从 SS 出发无法到达 TT 的情况。根据前人的经验,此时应输出 (n2)=n(n1)2\binom{n}{2}=\dfrac{n(n-1)}{2}


    P.S. 为了方便叙述,默认下文中的“路径”“最短路径”等均指“从 SSTT 的最短路径”

    分析

    F(i)F(i) 表示经过 ii 号点的路径数量,那么 FF 可以通过正反两次 Dijkstra 求出。

    容易想到一个满足题意的必要条件:F(A)+F(B)=F(T)F(A)+F(B)=F(T),即经过 A,BA,B 两个点的路径数之和等于总路径数。

    进一步,答案应为:满足 F(A)+F(B)=F(T)F(A)+F(B)=F(T) 且不存在一条路径同时经过 AABB 的无序点对 (A,B)(A,B) 的数量。(因为数量之和达到上限,所以只需保证每条路径只经过至多一个点,即:在 F(A)F(A)F(B)F(B) 中至多一个提供贡献)

    由于 F(A)+F(B)=F(T)F(A)+F(B)=F(T) 是一个简单的数量关系限制,容易处理。于是考虑如何求出不存在一条路径同时经过 AABB 的无序点对 (A,B)(A,B)(当然不是一个一个列举出,而是计算出能代表其特征的数据),再计算出其中满足 F(A)+F(B)=F(T)F(A)+F(B)=F(T) 的数量。

    题解

    首先任取一条 SSTT 的最短路径 PP,那么 AABB 中恰好一个在 PP 上。不妨设 AA 在路径 PP 上,则 BB 不在路径 PP 上。

    结论一

    对于固定的 BB,使得 AABB 满足特殊性质的全体 AA,在路径 PP 上是连续的一段。(xxyy 满足特殊性质”指“不存在一条路径同时经过 xxyy”,下同

    结论一证明

    X,Y,ZX,Y,Z 为路径 PP 上顺次(“顺次”指按从 SSTT 依次经过的顺序,不一定连续,下同)出现的三个点。假设 XXBBZZBB 都满足特殊性质,但 YYBB 不满足特殊性质。

    那么有两种情况:

    1. 存在一条最短路径 P1P_1,先经过 YY 再经过 BB
      • 将路径 P1P_1SSYY 的部分用路径 PPSSYY 的部分替换,得到路径 P2P_2。显然 P2P_2 也为最短路径,且 P2P_2 先经过 XX 再经过 BB,这与 XXBB 满足特殊性质矛盾。
    2. 存在一条最短路径 P3P_3,先经过 BB 再经过 YY
      • 将路径 P3P_3YYTT 的部分用路径 PPYYTT 的部分替换,得到路径 P4P_4。显然 P4P_4 也为最短路径,且 P4P_4 先经过 BB 再经过 ZZ,这与 ZZBB 满足特殊性质矛盾。

    因此假设不成立,则结论一成立。

    结论二

    X,YX,Y任意最短路径上顺次出现的两个点,则不存在最短路径先经过 YY 再经过 XX

    结论二证明

    SSXX 的最短距离为 aaXXYY 的最短距离为 eeYYTT 的最短距离为 bb,则 SSTT 的最短路径距离为 a+e+ba+e+b。再设 SSYY 的最短距离为 ccXXTT 的最短路径为 dd

    假设存在一条先经过 YY 再经过 XX 的最短路径,则 c+e+d=a+e+bc+e+d=a+e+b。进一步有 cac \le adbd \ge b,或 cac \ge adbd \le b

    cac \le a,则 SSTT 存在一条经过 YY 的路径,其长度为 c+ba+b<a+e+bc+b \le a+b < a+e+b

    dbd \le b,则 SSTT 存在一条经过 XX 的路径,其长度为 a+da+b<a+e+ba+d \le a+b < a+e+b

    这与 SSTT 的最短距离为 a+e+ba+e+b 矛盾,因此假设不成立。

    故结论二成立。

    推论

    由结论二可知:把所有最短路径看成有向路径,然后取它们的并集,所形成的有向图是一个 DAG(Directed Acyclic Graph,有向无环图)。

    求解不存在路径同时经过 xxyy 的无序点对 (x,y)(x,y)

    将路径 PP 上的点顺次记为 p1,p2,,ptp_1,p_2,\cdots,p_t,其中 tt 为路径 PP 上的点数。(Dijkstra 时记录每个点的前驱,即可求出某条最短路径 PP 上的 p1,p2,,ptp_1,p_2,\cdots,p_t

    由结论一,设路径 PP 上和 ii 号点有特殊性质的全体点为 pL(i),pL(i)+1,,pR(i)p_{L(i)},p_{L(i)+1},\cdots,p_{R(i)}L(i)>R(i)L(i)>R(i) 表示不存在),则有如下等式:

    L(i)=maxpkik+1L(i)=\max_{p_{_k} \to i}k+1 R(i)=minipkk1R(i)=\min_{i \to p_{_k}}k-1

    其中条件 xyx \to y 表示存在先经过 xx 再经过 yy 的最短路径

    为了便于计算,我们将其改成递推形式:

    $$L(i)=\begin{cases} j+1 \quad (i=p_j)\\ \max_{(k,i) \in E}L(k) \quad (\text{otherwise}) \end{cases}$$$$R(i)=\begin{cases} j-1 \quad (i=p_j)\\ \min_{(i,k) \in E}R(k) \quad (\text{otherwise}) \end{cases}$$

    其中条件 (x,y)E(x,y) \in E 表示 xxyy 之间存在直接相连的边,且存在一条最短路径先经过 xx 再经过 yy

    由推论可知,由于有向最短路径的并集形成的图是 DAG,可以使用拓扑排序LLRR 进行转移。(即:只考虑最短路径可能经过的边,进行拓扑排序)

    求解其中满足 FF 之和等于 F(T)F(T) 的无序点对数量

    由上一部分知,所有的、不存在路径同时经过 xxyy 的无序点对 (x,y)(x,y) 为全体 (i,pj)(i,p_j),其中 ii 不在路径 PP 上且 j[l(i),r(i)]j \in [l(i),r(i)]

    至此,只需解决最后一个问题:对于每个 ii,求出满足 j[l(i),r(i)]j \in [l(i),r(i)]F(i)+F(pj)=F(T)F(i)+F(p_j)=F(T)jj 的数量。

    拿一个 map 当桶,在计算 pl(i)p_{l(i)} 的贡献前把值 F(i)F(i) 加入,在计算 pr(i)p_{r(i)} 的贡献后把值 F(i)F(i) 删除,F(i)+F(pj)=F(T)F(i)+F(p_j)=F(T)ii 的数量即为计算时 map 中值 F(T)F(pj)F(T)-F(p_j) 的数量。

    注意:在本题的限制下,起点 SS 到终点 TT 的最短路径数量 FF 可以达到指数级别。因此,要将最短路径的数量 FF 对大质数取模,否则可以被【前言】中提到的 Hack 数据 Hack。

    时间复杂度分析

    堆优化 Dijkstra 是 O(mlogm)O(m\log{m}) 的,拓扑排序是 O(m)O(m) 的,map 的加入、删除、查询次数为 O(n)O(n),因此总时间复杂度为 O(mlogm+nlogn)O(m\log{m}+n\log{n})

    代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,S,T;
    const int max_n=5e4+5;
    const int max_m=5e4+5;
    int End[max_m<<1],Last[max_n],Next[max_m<<1],Len[max_m<<1],e;
    inline void add_edge(int x,int y,int z)
    {
    	End[++e]=y,Next[e]=Last[x],Last[x]=e,Len[e]=z;
    	End[++e]=x,Next[e]=Last[y],Last[y]=e,Len[e]=z;
    }
    typedef pair<long long,int> P;
    priority_queue<P,vector<P>,greater<P> > Q;
    long long dis[2][max_n];
    const int mod=1e9+7;
    inline void add(int &a,int b)
    {
    	a=a+b-(a+b>=mod?mod:0);
    }
    inline int get_dif(int a,int b)
    {
    	return a-b+(a<b?mod:0);
    }
    int f[2][max_n],pre[max_n];
    inline void Dijkstra(int op)
    {
    	for(int i=1;i<=n;++i)
    		dis[op][i]=1e18;
    	if(!op)
    	{
    		dis[op][S]=0,f[op][S]=1;
    		Q.push(P(0,S));
    	}
    	else
    	{
    		dis[op][T]=0,f[op][T]=1;
    		Q.push(P(0,T));
    	}
    	while(Q.size())
    	{
    		long long d=Q.top().first;
    		int x=Q.top().second;
    		Q.pop();
    		if(dis[op][x]<d)
    			continue;
    		for(int i=Last[x];i;i=Next[i])
    		{
    			int y=End[i];
    			if(d+Len[i]<dis[op][y])
    			{
    				dis[op][y]=d+Len[i];
    				f[op][y]=f[op][x];
    				if(op)
    					pre[y]=x;
    				Q.push(P(dis[op][y],y));
    			}
    			else if(d+Len[i]==dis[op][y])
    				add(f[op][y],f[op][x]);
    		}
    	}
    }
    inline bool check(int op,int x,int y,int w)
    {
    	return dis[op][x]+w+dis[op^1][y]==dis[0][T];
    }
    int p[max_n],tot,l[max_n],r[max_n],d[max_n],que[max_n],head,tail;
    inline void TopSort(int op)
    {
    	for(int x=1;x<=n;++x)
    		for(int i=Last[x];i;i=Next[i])
    		{
    			int y=End[i];
    			if(check(op,x,y,Len[i]))
    				++d[y];
    		}
    	head=1,tail=0;
    	for(int i=1;i<=n;++i)
    	{
    		if(!d[i])
    			que[++tail]=i;
    	}
    	while(head<=tail)
    	{
    		int x=que[head++];
    		for(int i=Last[x];i;i=Next[i])
    		{
    			int y=End[i];
    			if(check(op,x,y,Len[i]))
    			{
    				op?r[y]=min(r[x],r[y]):l[y]=max(l[x],l[y]);
    				if(!--d[y])
    					que[++tail]=y;
    			}
    		}
    	}
    	assert(tail==n);
    }
    int F[max_n];
    vector<int> id_l[max_n],id_r[max_n];
    map<int,int> cnt;
    int main()
    {
    	scanf("%d%d%d%d",&n,&m,&S,&T);
    	for(int i=1;i<=m;++i)
    	{
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		add_edge(u,v,w);
    	}
    	Dijkstra(0);
    	if(dis[0][T]==1e18)
    	{
    		printf("%lld\n",n*(n-1ll)>>1);
    		return 0;
    	}
    	Dijkstra(1);
    	for(int i=S;i;i=pre[i])
    		p[++tot]=i,l[i]=tot+1,r[i]=tot-1;
    	for(int i=1;i<=n;++i)
    	{
    		if(dis[0][i]+dis[1][i]==dis[0][T])
    			F[i]=1ll*f[0][i]*f[1][i]%mod;
    		if(!l[i])
    			l[i]=1,r[i]=tot;
    	}
    	TopSort(0),TopSort(1);
    	for(int i=1;i<=n;++i)
    	{
    		if(l[i]<=r[i])
    		{
    			id_l[l[i]].push_back(i);
    			id_r[r[i]].push_back(i);
    		}
    	}
    	long long ans=0;
    	for(int i=1;i<=tot;++i)
    	{
    		for(vector<int>::iterator it=id_l[i].begin();it!=id_l[i].end();++it)
    			++cnt[F[*it]];
    		ans+=cnt[get_dif(F[T],F[p[i]])];
    		for(vector<int>::iterator it=id_r[i].begin();it!=id_r[i].end();++it)
    			--cnt[F[*it]];
    	}
    	printf("%lld\n",ans);
    	return 0;
    }
    
    • 1

    信息

    ID
    3023
    时间
    1000ms
    内存
    1000MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者