题目描述
\(T\) 组数据, \(n\) 个点 \(m\) 条边的无向连通图,边有边权,保证无重边、自环。
\(q\) 次询问,给定 \((u,v,k)\) ,求所有 \(u\to v\) 的路径中,第 \(k\) 大边权的最小值。
数据范围
- \(1\le T\le 100\) 。
- \(2\le n,\sum n\le 400\) 。
- \(n-1\le m\le\frac{n(n-1)}2\) 。
- \(1\le q,\sum q\le 3\cdot 10^5\) 。
- \(1\le u\neq v\le n,k\ge 1\) ,保证任意一条 \(u\to v\) 的路径至少有 \(k\) 条边。
时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{1024MB}\) 。
分析
先考虑 \(\texttt{E1}\) 怎么做。
询问看到 "最大值最小" 显然需要二分答案,判定 "第 \(k\) 大边权能否小于等于 \(mid\)" 等价于 "用不超过 \(k-1\) 条权值大于 \(mid\) 的边能否将 \(u,v\) 连通" 。
将边按权值升序排序,由于 \(m\) 很小,我们只需对 \(\forall 0\le i\le m\) ,预处理将前 \(i\) 条边视为 \(0\) ,后 \(m-i\) 条边视为 \(1\) 时的任意两点最短路。
初始跑 \(\texttt{floyd}\) 算法,后续每次将一条权值为 \(1\) 的边修改为 \(0\) ,我们需要对 \(n^2\) 个点对进行松弛,即单独考虑经过 \((u,v)\) 这条边时的答案。
时间复杂度 \(\mathcal O(mn^2+q\log m)\) ,空间复杂度 \(\mathcal O(mn^2)\) 。
对于 \(\texttt{E2}\) ,由于 \(m\) 很大,时间和空间都无法承受 \(\mathcal O(mn^2)\) 的复杂度。
考虑删减状态。如果在添加 \((u,v)\) 这条边之前, \(u,v\) 的距离已经是零(换言之原图可以通过比这条边权值更小的边将 \(u,v\) 连通),那么这条边没有任何作用,可以直接删掉。
将 \(0\) 边连接的点视为连通块,那么每加一条边相当于合并两个连通块,因此只会添加 \(n-1\) 条边。
时间复杂度 \(\mathcal O(n^3+q\log n)\) ,空间复杂度 \(\mathcal O(n^3)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=405,inf=1e9;
int k,m,n,q,t,u,v,w;
int c[maxn],d[maxn][maxn][maxn];
struct edge
{int u,v,w;
};
vector<edge> e;
void chmin(int &x,int y)
{if(x>y) x=y;
}
int main()
{for(scanf("%d",&t);t--;){scanf("%d%d%d",&n,&m,&q),e.clear();for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[0][i][j]=i!=j?inf:0;for(int i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);d[0][u][v]=d[0][v][u]=1,e.push_back({u,v,w});}for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)chmin(d[0][i][j],d[0][i][k]+d[0][k][j]);sort(e.begin(),e.end(),[&](edge x,edge y){return x.w<y.w;});m=0;for(auto [u,v,w]:e){if(!d[m][u][v]) continue;c[++m]=w;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){d[m][i][j]=d[m-1][i][j];chmin(d[m][i][j],d[m-1][i][u]+d[m-1][v][j]);chmin(d[m][i][j],d[m-1][i][v]+d[m-1][u][j]);}}while(q--){scanf("%d%d%d",&u,&v,&k);int l=0,r=m;while(r-l>1){int mid=(l+r)>>1;d[mid][u][v]<k?r=mid:l=mid;}printf("%d ",c[r]);}putchar('\n');}return 0;
}
总结
- 赛时有点脑抽,发现答案一定是最小生成树上的边权(即答案只有 \(\mathcal O(n)\) 种),但是没想到有用的边也只有 \(\mathcal O(n)\) 条。于是思路一直卡在怎么求 \(\mathcal O(n)\) 张图的任意两点最短路上,但实际上这是做不到的。