题解: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 就只用在每一个强联通分量中跑了。
总结一下整体过程:
- 求强联通分量,缩点;
- 在每个强联通分量内跑全源最短路;
- 在每个强联通分量求每两点之间距离的最大值,这个值加一就是对答案的贡献;
- 对每个强联通分量的贡献求和。
做完了。
代码
#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;
}
完结撒花花!
本文来自博客园,作者:Circle_Table,转载请注明原文链接:https://www.cnblogs.com/Circle-Table/articles/19486883

浙公网安备 33010602011771号