1 条题解

  • 0
    @ 2026-9-26 1:26:07

    这边是题目传送门喵!

    欢迎在博客园中食用,观感可能更佳。

    练差分约束的时候遇到的好题。

    题意简述

    nn 个数,假设第 ii 个数为 tit_i。给定 m1m_1 条形如 tu=tv−1 t_u = t_v - 1 关系,以及 m2m_2 条形如 tu≤tvt_u \leq t_v 的关系。求这 nn 个 tit_i 中最多有几种不同的 tit_i。

    思路

    这种形式就很像差分约束,所以也不过多阐述为什么要用差分约束。

    首先,不难看出建边会有三种边权分别是 −1,0,1-1,0,1 而且只会出现这三种。继续考虑,受到这个题的启发,想到了用 Tarjan 先缩点,在每一个强联通分量内,每两个点都可以互相到达,那么就可以轻易算出负环和正环,而且还能得出一个强联通分量内 tit_i 的种类数。

    之所以能求出这个种类数的原因,是因为我们能算出一个强联通分量的 tit_i 的极差,又因为边权只有三种,则在最大值与最小值之间的每一个就都能取到。

    然后注意到数据范围,应该是给 Floyd 算法用的,但这个时候我的 SPFA 板子都打好了,这就使之前一直只写 SPFA 的我沉思片刻。

    其实差分约束的实质是利用最短路中是否有负环,或者最长路中是否有正环来检验约束条件是否能够成立。但显然如果用 Floyd 直接求出全源最短路或最长路,自然也可以达到所需的效果。

    继续看数据范围,就会发现大概率是存在重边的。那么建边的时候就要注意取最值。本题解是用最短路写的,因此取最小值,同时存在负环时就判断无解。

    通过之前的推断,不同强联通分量之间互不影响,因此我们对每一个强联通分量求出极差,不过显然由于左右端点能取到,那么这个强联通分量对答案的贡献就是极差加一。最后将每个强联通分量的贡献求和即可。

    那么极差又应该怎么求呢?显然可以通过强联通分量中每两点间距离的最大值求出,显然距离最远的两个点就是左右端点。因此我们跑 Floyd 就只用在每一个强联通分量中跑了。

    总结一下整体过程:

    1. 求强联通分量,缩点;
    2. 在每个强联通分量内跑全源最短路;
    3. 在每个强联通分量求每两点之间距离的最大值,这个值加一就是对答案的贡献;
    4. 对每个强联通分量的贡献求和。

    做完了。

    代码

    #include<bits/stdc++.h>
    #define ll long long
    #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
    using namespace std;
    const int N=605;
    const int inf=0x3f3f3f3f;
    
    int n,m1,m2,dis[N][N];
    int ans,f[N]; // 答案与强联通分量的贡献,即极差 +1
    
    void add(int u,int v,int w){dis[u][v]=min(dis[u][v],w);}
    
    // Tarjan 部分
    int dfn[N],low[N],idx;
    stack<int>st;
    bool ins[N];
    int col[N],color; // 新图,color 是强联通分量数
    
    void tarjan(int u){
        dfn[u]=low[u]=++idx;
        st.push(u),ins[u]=1;
        for(int v=1;v<=n;v++){
            if(dis[u][v]==inf)continue;
            if(!dfn[v]){
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(ins[v]){
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u]){
            color++;
            int v;
            do{
                v=st.top(),st.pop();
                ins[v]=0;
                col[v]=color;
            }while(u!=v);
        }
        return;
    }
    
    int main(){
        ios;cin>>n>>m1>>m2;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j)dis[i][j]=0;
                else dis[i][j]=inf;
            }
        }
        // 建图
        int u,v;
        for(int i=1;i<=m1;i++){ // u=v-1
            cin>>u>>v;
            add(u,v,1);  // v<=u+1    ,原形是 u>=v-1
            add(v,u,-1); // u<=v+(-1) ,原形是 u<=v-1
        }
        for(int i=1;i<=m2;i++){ // u<=v
            cin>>u>>v;
            add(v,u,0); // u<=v+0
        }
    
        for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
    
        // Floyd 部分
        for(int k=1;k<=n;k++){
            for(int i=1;i<=n;i++){
                if(col[i]!=col[k]||dis[i][k]==inf)continue;
                for(int j=1;j<=n;j++){
                    if(col[i]==col[j])dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                }
            }
        }
    
        // 统计贡献
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j&&dis[i][j]<0){
                    cout<<"NIE\n";
                    return 0;
                }
                if(col[i]==col[j]){
                    int p=col[i];
                    f[p]=max(f[p],dis[i][j]+1);
                }
            }
        }
        for(int i=1;i<=n;i++)ans+=f[i];
        cout<<ans<<'\n';
        return 0;
    }
    
    

    完结撒花花!

    • 1

    信息

    ID
    4453
    时间
    7500ms
    内存
    64MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者