最小圈
二分答案加DFS版SPFA判负环
正文
// 原题:https://oj.yecheng.tv/p/T1506
// 题意:给 n 点 m 条带实权有向边,求图中所有环的平均边权最小值,输出到小数点后 8 位。
// 思路:二分答案 mid,把边权改成 w - mid,若存在负环说明有环均值小于 mid,上界下压,否则抬下界。
// 1. 判负环用 DFS 版 SPFA,回溯栈上的点构成环时立刻返回,比普通队列 SPFA 快很多。
// 2. 所有点的 dis 初始化为 0 等价于加了一个指向所有点的虚拟源点,能判出全图的负环。
// 复杂度:O(log V * n * m) 时间 / O(n + m) 空间
// 易错点:边权是实数,读入要用 double,二分次数要够(60 次左右)才能保证 8 位小数。
// 易错点:退出 DFS 前必须把 instk 清掉,否则会把正常的跨子树边误判成环。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
struct Edge{
int to;
double w;
};
vector<Edge> g[MAXN];
double dis[MAXN];
bool instk[MAXN];
int n, m;
bool dfs(int u, double mid){
instk[u] = true;
for(int i = 0; i < (int)g[u].size(); i++){
int v = g[u][i].to;
double w = g[u][i].w - mid;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if(instk[v]) return true;
if(dfs(v, mid)) return true;
}
}
instk[u] = false;
return false;
}
bool check(double mid){
for(int i = 1; i <= n; i++){
dis[i] = 0.0;
instk[i] = false;
}
for(int i = 1; i <= n; i++){
if(dfs(i, mid)) return true;
}
return false;
}
int main(){
if(!(cin >> n >> m)) return 0;
for(int i = 0; i < m; i++){
int u, v;
double w;
cin >> u >> v >> w;
Edge e;
e.to = v;
e.w = w;
g[u].push_back(e);
}
double lo = -1e7;
double hi = 1e7;
for(int it = 0; it < 60; it++){
double mid = (lo + hi) / 2.0;
if(check(mid)) hi = mid;
else lo = mid;
}
cout << fixed << setprecision(8) << lo << "\n";
return 0;
}
一本通·提高篇的其它内容
- 活动安排
- 种树
- 喷水装置
- 加工生产调度
- 智力大冲浪
- 数列极差
- 数列分段
- 线段
- 家庭作业
- 钓鱼
- 糖果传递
- 愤怒的牛
- Best Cow Fences
- 曲线
- 数列分段 II
- 扩散
- 灯泡
- 传送带
- 数的划分
- 生日蛋糕
- 小木棍
- Addition Chains
- 埃及分数
- 平板涂色
- 质数方阵
- 靶形数独
- 电路维修
- 魔板
- Knight Moves
- 棋盘游戏
- Keyboarding
- 移动玩具
- 山峰和山谷
- Oulipo
- 图书管理
- Power Strings
- Seekthe Name, Seek the Fame
- Friends
- A Horrible Poem
- Beads
- Antisymmetry
- 门票
- 收集雪花
- 剪花布条
- Power Strings
- Radio Transmission
- OKR-Periods of Words
- 似乎在梦中见过的样子
- Censoring
- Phone List
- The XOR Largest Pair
- Nikitosh 和异或
- Immediate Decodability
- L 语言
- Secret Message 秘密信息
- 背单词
- The Xor-longest Path
- Keywords Search
- 玄武密码
- Censoring