题解:P3530 [POI 2012] FES-Festival

这边是题目传送门喵!

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

题意简述

\(n\) 个数,假设第 \(i\) 个数为 \(t_i\)。给定 \(m_1\) 条形如 $ t_u = t_v - 1 $ 关系,以及 \(m_2\) 条形如 \(t_u \leq t_v\) 的关系。求这 \(n\)\(t_i\) 中最多有几种不同的 \(t_i\)

思路

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

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

之所以能求出这个种类数的原因,是因为我们能算出一个强联通分量的 \(t_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;
}

完结撒花花!

posted @ 2026-01-15 13:34  Circle_Table  阅读(4)  评论(0)    收藏  举报