题目:
对于边带权的有向图,找出一个点数最小的环,使得环上的边权和为负.
2 <= n <= 300.
题解:
我们可以考虑从小到大枚举答案.
然后每次枚举更大的答案的时候就从当前的较小的答案更新过去.
更具体一点,可以设f[i][j]表示当前的步数下从i走到j的最短路.
每次更新本质就是一个简单的动态规划的状态转移.
但是这样复杂度是\(O(n^4)\)的.
肯定跑不过去.
更近一步地,从刚才的思路转变一下.
我们设\(f[d][i][j]\)表示\(i \to j\)走\(2^d\)步时的最短路.
我们可以在\(O(n^3\log n)\)的复杂度内预处理出来整个数组.震惊! \(n^3\log n\)竟可以跑过300 !!!
然后我们利用这个数组将上面的从小到大枚举答案变为类似二分的过程!
也就是说我们可以枚举答案的二进制位.
不难发现如果\((010)_2\)成立那么\((100)_2\)也成立,所以答案具有单调性.
所以二分即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;bool flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 510;
const int inf = 0x3f3f3f3f;
int dis[9][maxn][maxn];
int f[maxn][maxn],g[maxn][maxn];
int main(){
int n,m;read(n);read(m);
int u,v,w;
rep(i,1,n) rep(j,1,n) if(i != j) dis[0][i][j] = inf;
rep(i,1,m){
read(u);read(v);read(w);
dis[0][u][v] = w;
}
rep(d,1,8){
rep(i,1,n) rep(j,1,n){
if(i != j) dis[d][i][j] = inf;
else dis[d][i][j] = 0;
}
rep(k,1,n) rep(i,1,n) rep(j,1,n){
dis[d][i][j] = min(dis[d-1][i][k]+dis[d-1][k][j],dis[d][i][j]);
}
}
rep(i,1,n) rep(j,1,n){
if(i != j) g[i][j] = inf;
else g[i][j] = 0;
}
bool flag;
int ans = 0;
per(d,8,0){
rep(i,1,n) rep(j,1,n) f[i][j] = g[i][j];
rep(k,1,n) rep(i,1,n) rep(j,1,n){
f[i][j] = min(f[i][j],min(g[i][k]+dis[d][k][j],dis[d][i][k]+g[k][j]));
}
flag = false;
rep(i,1,n) if(f[i][i] < 0){
flag = true;
break;
}
if(flag) continue;
rep(i,1,n) rep(j,1,n) g[i][j] = f[i][j];
ans |= 1 << d;
}
if(++ans > n) puts("0");
else printf("%d\n",ans); return 0;
}