方向:枚举点的个数,找出其中边权和为负数的最小值。
直接枚举显然会超时,不妨考虑使用倍增凑出点的个数(注意:点数不完全有单调性,但是后面会提到如何转化处理)。
先预处理出 \(dis_{t,i,j}\) 表示经过 \(t\) 条边,从 \(i\rightarrow j\) 的最短路长度。那么类似 \(Floyed\) 显然有如下式子:
\[dis_{t,i,j}=\min_{1\le k\le n}\{dis_{t-1,i,k}+dis_{t-1,k,j}\} \]可以在 \(O(n^3\log n)\) 中算出。
接下来计算 \(f_{i,j}\) 表示从 \(i\rightarrow j\) 的最短路,点数则在倍增中处理,如果存在 \(f_{i,i}<0\),说明有包含 \(i\) 的负环。
接着考虑倍增基本套路:从大到小枚举 \(t\),尝试将当前点数的 \(dis\) 值加入 \(f\) 中,并考虑有无负环,如果有,则不做变化;如果没有,就将当前点数的 \(2\) 次幂加入答案,最后将答案 \(+1\) 即可。(同 \(LCA\) 套路)。
使用如下式子即可将 \(dis\) 加入 \(f\):
\[f'_{i,j}=\min\{f_{i,k}+dis_{t,k,j},dis_{t,i,k}+f_{k,j}\} \]注意,\(f'\) 和 \(f\) 是两个东西,如果两边都写 \(f\),那么就不止加 \(2^t\) 条边。
如果没有负环,再将 \(f'\) 的值保存进 \(f\) 里。
但是这样还有瑕疵,因为点数不具备单调性,\(3\) 个点可以,可能 \(4\) 个点就不行。考虑在每个点上加一个自环,即:\(i\rightarrow i\),\(f_{i,i}=dis_{0,i,i}=0\),那么 \(dis_{t,i,j}\) 实际上就转化为至多经过 \(2^t\) 条边,从 \(i\rightarrow j\) 的最短路径。(因为其中可能走过一些自环边,在原题中相当于没走),从恰好转化为至多后,必然符合单调性,可以倍增。
总结:
- 单调性转化:如果恰好的数量不符合单调性,可以考虑至多/至少的数量,这样一定符合单调性;
- 恰好与至多至少转化:如果题目含有两个需考虑的值,而做法是考虑其中一种,那么可以往其中加入对一个值有影响但是对另外一个值无影响的变量;
- 倍增:题目具备单调性,求最值。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=310;
int n,m;
int dis[10][N][N],f[N][N],g[N][N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++) {
f[i][i]=0;
}
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=m;i++) {
int u,v,w; cin>>u>>v>>w;
dis[0][u][v]=w;
}
for(int i=1;i<=n;i++) dis[0][i][i]=0;
for(int t=1;t<=9;t++) {
for(int k=1;k<=n;k++) {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
dis[t][i][j]=min(dis[t][i][j],dis[t-1][i][k]+dis[t-1][k][j]);
}
}
}
}
int ans=0;
for(int t=9;t>=0;t--) {
memcpy(g,f,sizeof(f));
for(int k=1;k<=n;k++) {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
g[i][j]=min(g[i][j],min(f[i][k]+dis[t][k][j],dis[t][i][k]+f[k][j]));
}
}
}
int pd=0;
for(int i=1;i<=n;i++) {
if(g[i][i]<0) {pd=1;break;}
}
if(!pd) {
ans|=(1<<t);
memcpy(f,g,sizeof(g));
}
}
if(ans>=n) cout<<0;
else cout<<ans+1;
return 0;
}
标签:高手,int,题解,负环,点数,rightarrow,单调,dis
From: https://www.cnblogs.com/zhangyuzhe/p/18114588