高手训练 负环 题解

题目链接

方向:枚举点的个数,找出其中边权和为负数的最小值。

直接枚举显然会超时,不妨考虑使用倍增凑出点的个数(注意:点数不完全有单调性,但是后面会提到如何转化处理)。

先预处理出 dist,i,j 表示经过 t 条边,从 ij 的最短路长度。那么类似 Floyed 显然有如下式子:

dist,i,j=min1kn{dist1,i,k+dist1,k,j}

可以在 O(n3logn) 中算出。

接下来计算 fi,j 表示从 ij 的最短路,点数则在倍增中处理,如果存在 fi,i<0,说明有包含 i 的负环。

接着考虑倍增基本套路:从大到小枚举 t,尝试将当前点数的 dis 值加入 f 中,并考虑有无负环,如果有,则不做变化;如果没有,就将当前点数的 2 次幂加入答案,最后将答案 +1 即可。(同 LCA 套路)。

使用如下式子即可将 dis 加入 f

fi,j=min{fi,k+dist,k,j,dist,i,k+fk,j}

注意,ff 是两个东西,如果两边都写 f,那么就不止加 2t 条边。

如果没有负环,再将 f 的值保存进 f 里。

但是这样还有瑕疵,因为点数不具备单调性,3 个点可以,可能 4 个点就不行。考虑在每个点上加一个自环,即:iifi,i=dis0,i,i=0,那么 dist,i,j 实际上就转化为至多经过 2t 条边,从 ij 的最短路径。(因为其中可能走过一些自环边,在原题中相当于没走),从恰好转化为至多后,必然符合单调性,可以倍增。

总结:

  • 单调性转化:如果恰好的数量不符合单调性,可以考虑至多/至少的数量,这样一定符合单调性;
  • 恰好与至多至少转化:如果题目含有两个需考虑的值,而做法是考虑其中一种,那么可以往其中加入对一个值有影响但是对另外一个值无影响的变量;
  • 倍增:题目具备单调性,求最值。

代码:

#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;
}
posted @   2017BeiJiang  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示