题解:P13519 [KOI 2025 #2] 通行费

这边是题目传送门喵!

题意简述

原题干中的大写 \(N,M\) 在题解中均用对应的小写字母 \(n,m\) 表示。

对于 \(n\) 个点,\(m\) 条边的无向图,初始每条边的边权都为 \(0\),按照输入的顺序依次将边权改为 \(1\)。求每次修改边权后图上任意两点之间最短路的长度。

思路

注意到 \(2 \leq n \leq 500\),不难想到用 Floyd 来写。

  1. 首先最暴力的写法,对每次的修改边权跑一遍 Floyd 就可以了。然后写了一个 \(calc()\) 函数来计算每一时刻的答案。

    为什么要跑 \(m\) 遍 Floyd 呢?显然后面增加了边权,最短路是会增加,而如果直接用目前的数据继续跑最短路就会导致答案偏小。对于每一条边都跑一次 Floyd 的复杂度是 \(\Theta (m \times n^3)\),肯定是会超时被创飞(但我也不知道为什么会 RE 最后一个子任务)

  2. 接下来考虑优化。很容易就会有一个想法,“时光倒流”,从后面往前面操作,也是一个很经典的 Trick,将原来的边权加一变为边权减一。显然,这个时候最短路的长度是单调递减的,我们就不用再全部重新跑一遍 Floyd 了,而只用在一开始就预处理之后对部分进行修改即可。

    对于每一次边权减一的操作,需要修改这两点之间的距离,同时还要对其他点进行修改——如果有利用这条边作为其最短路中的某一条边的两点,这两点就也需要更新。因此相当于将 Floyd 的 \(k\) 循环定下来再跑里面的 \(i,j\) 循环。

    这一段的代码可以得到 \(55\)

    #include<bits/stdc++.h>
    #define ll long long
    #define fi first
    #define se second
    #define pii pair<int,int>
    #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
    using namespace std;
    const int N=505;
    const int inf=0x3f3f3f3f;
    int n,m,dis[N][N];
    stack<pii>st;
    stack<int>ans;
    
    int calc(){
        int res=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                res+=dis[i][j];
            }
        }
        return res;
    }
    
    int main(){
        ios;cin>>n>>m;
        int u,v;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i!=j)dis[i][j]=inf;
                else dis[i][j]=0;
            }
        }
        for(int i=1;i<=m;i++){
            cin>>u>>v;
            st.push({u,v}); // 先进后出,对应时光倒流
            dis[u][v]=dis[v][u]=1;
        }
        for(int k=1;k<=n;k++){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                }
            }
        }
        
        while(!st.empty()){
            ans.push(calc()); // 答案自然也要倒过来
            int u=st.top().fi;
            int v=st.top().se;
            st.pop();
            if(st.empty())break;
            dis[u][v]=dis[v][u]=0;
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    dis[i][j]=min(dis[i][j],dis[i][u]+dis[u][j]);
                }
            }
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    dis[i][j]=min(dis[i][j],dis[i][v]+dis[v][j]);
                }
            }
        }
        while(!ans.empty()){
            cout<<ans.top()<<'\n';
            ans.pop();
        }
        return 0;
    }
    

    但是这样的代码,复杂度主要在于修改的部分,\(\Theta (m\times n^2)\)\(m \leq \frac{n(n-1)}{2}\) 的数据范围下依旧无法接受。继续考虑优化。

  3. 既然复杂度主要在于修改的部分就优化这一段。既然优化复杂了,那就是说有的时候不需要优化。例如下图:

    在这个图中,如果在修改了 \((1,4),(1,5)\) 这两条边后,则修改 \((4,5)\) 这条边不会影响答案。也就是说,若两点之间已经存在长度为 \(0\) 的路径,则将这两点之间的边权修改为 \(0\) 时不会对答案产生影响,则可以跳过此操作。

    在进行了这样一步优化之后,显然我们要操作的次数变成了 \(n-1\) 次,相当于形成一颗全部边权为 \(0\) 的树的过程。

    到这里思路就差不多了。代码实现也就是把上面的代码中的 \(while\) 循环中添加跳过的条件,复杂度变为 \(\Theta (n^3)\),于是就可以通过了。

代码实现:

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int N=505;
const int inf=0x3f3f3f3f;
int n,m,dis[N][N];
stack<pii>st;
stack<int>ans;

int calc(){
    int res=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            res+=dis[i][j];
        }
    }
    return res;
}

int main(){
    ios;cin>>n>>m;
    int u,v;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i!=j)dis[i][j]=inf;
        }
    }
    for(int i=1;i<=m;i++){
        cin>>u>>v;
        st.push({u,v});
        dis[u][v]=dis[v][u]=1;
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    int res=calc();
    while(!st.empty()){
        ans.push(res);
        int u=st.top().fi;
        int v=st.top().se;
        st.pop();
        if(st.empty())break;
        if(!dis[u][v])continue;
        dis[u][v]=dis[v][u]=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][u]+dis[u][j]);
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][v]+dis[v][j]);
            }
        }
        res=calc();
    }
    while(!ans.empty()){
        cout<<ans.top()<<'\n';
        ans.pop();
    }
    return 0;
}

完结撒花花!

posted @ 2026-02-04 13:31  Circle_Table  阅读(6)  评论(0)    收藏  举报