peiwenjun's blog 没有知识的荒原

CF2057E2 Another Exercise on Graphs (hard version) 题解

题目描述

\(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)\) 张图的任意两点最短路上,但实际上这是做不到的。

posted on 2025-01-06 20:44  peiwenjun  阅读(63)  评论(0)    收藏  举报

导航