BZOJ 4773 负环
2017.10.24
2017.10.24
给你一个有向图,求点数最小的边权和为负数的环。
这题因为和点以及边都有关系,而且点数小的吓人所以可以考虑使用倍增Floyd。
在倍增Floyd后得到了$dis_{k,i,j}$表示走$\le 2^k$条边从i到j的最短距离。
本来是严格的之后不会做,指点后发现其实这个东西可以设的宽松一点的……之后就惊奇的发现这个东西是单调的了。
对于二进制+单调有两种做法:一种是倍增,使用类似LCA的方法可以枚举到所有的点;第二种是二分。
这里使用二进制倍增的方法。二进制倍增的时候我竟然特判了每一位是不是N的组成部分……倍增LCA也不这样啊……千万不能写啊qwq
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,lg[310],ans,ix,iy,iz;
struct dist
{
int v[310][310];
dist() {memset(v,0x3f,sizeof v);}
dist operator*(const dist &tx)const
{
dist ret;
for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
ret.v[i][j]=min(ret.v[i][j],v[i][k]+tx.v[k][j]);
return ret;
}
}dis[15],res;
bool check(dist &tx)
{
for(int i=1;i<=n;++i) if(tx.v[i][i]<0) return true;
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) dis[0].v[i][i]=res.v[i][i]=0;
for(int i=1;i<=m;++i) scanf("%d%d%d",&ix,&iy,&iz),dis[0].v[ix][iy]=iz;
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for(int i=1;i<=lg[n];++i) dis[i]=dis[i-1]*dis[i-1];
for(int i=lg[n];~i;i--)
{
dist tmp=dis[i]*res;
if(!check(tmp)) res=tmp,ans+=(1<<i);
}
res=res*dis[0];
if(!check(res)) ans=-1;
printf("%d\n",ans+1);
return 0;
}