负环
求负环的常用方法,基于spfa:
①统计每个点入队的次数,如果某个点入队n次,则说明存在负环。
②统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则说明存在负环。
(dis的距离初始化不管为多少都不影响求负环)
通常用第二种方法。
1 bool spfa() 2 { 3 memset(dist, 0, sizeof dist); 4 memset(cnt, 0, sizeof cnt); 5 memset(st, 0, sizeof st); 6 7 int hh = 0, tt = 0; 8 for (int i = 1; i <= n; i ++ ) 9 { 10 q[tt ++ ] = i; 11 st[i] = true; 12 } 13 14 while (hh != tt) 15 { 16 int t = q[hh ++ ]; 17 if (hh == N) hh = 0; 18 st[t] = false; 19 20 for (int i = h[t]; ~i; i = ne[i]) 21 { 22 int j = e[i]; 23 if (dist[j] > dist[t] + w[i]) 24 { 25 dist[j] = dist[t] + w[i]; 26 cnt[j] = cnt[t] + 1; 27 if (cnt[j] >= n) return true; 28 if (!st[j]) 29 { 30 q[tt ++ ] = j; 31 if (tt == N) tt = 0; 32 st[j] = true; 33 } 34 } 35 } 36 } 37 38 return false; 39 }
https://www.acwing.com/problem/content/363/
acwing 361.观光奶牛

该题是01分数规划,和二分合在一起做。由题目,要求要求的是$\frac{\sum_{i = 1}^{n}f_{i}}{\sum_{i = 1}^{n}t_{i}}$的最大值,所以我们在区间$[L, R]$上,对其进行二分,假设中点是$Mid$,要找到不满满足$\frac{\sum_{i = 1}^{n}f_{i}}{\sum_{i = 1}^{n}t_{i}} > Mid$的最大值,对公式进行化简:
$\sum_{i = 1}^{n}f_{i} > Mid \sum_{i = 1}^{n}t_{i} \leftrightarrow \sum_{i = 1}^{n}f_{i} - Mid \sum_{i = 1}^{n}t_{i} > 0$,由于我们可以将点权合并到出边的边权上,可以进一步化简:
$\sum_{i = 1}^{n}(f_{i} - Mid*t_{i}) > 0$,所以该问题等价于判断图中是否存在正环,如果存在,就到二分的右边找。如果不存在就到二分的左边找。
而求存在正环,可以将所有的权重取反值,也可以直接改为求最长路。
1 #include <cstring> 2 #include <iostream> 3 #include <algorithm> 4 5 using namespace std; 6 7 const int N = 1010, M = 5010; 8 9 int n, m; 10 int wf[N]; 11 int h[N], e[M], wt[M], ne[M], idx; 12 double dist[N]; 13 int q[N], cnt[N]; 14 bool st[N]; 15 16 void add(int a, int b, int c) 17 { 18 e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; 19 } 20 21 bool check(double mid) 22 { 23 memset(dist, 0, sizeof dist); 24 memset(st, 0, sizeof st); 25 memset(cnt, 0, sizeof cnt); 26 27 int hh = 0, tt = 0; 28 for (int i = 1; i <= n; i ++ ) 29 { 30 q[tt ++ ] = i; 31 st[i] = true; 32 } 33 34 while (hh != tt) 35 { 36 int t = q[hh ++ ]; 37 if (hh == N) hh = 0; 38 st[t] = false; 39 40 for (int i = h[t]; ~i; i = ne[i]) 41 { 42 int j = e[i]; 43 if (dist[j] < dist[t] + wf[t] - mid * wt[i]) 44 { 45 dist[j] = dist[t] + wf[t] - mid * wt[i]; 46 cnt[j] = cnt[t] + 1; 47 if (cnt[j] >= n) return true; 48 if (!st[j]) 49 { 50 q[tt ++ ] = j; 51 if (tt == N) tt = 0; 52 st[j] = true; 53 } 54 } 55 } 56 } 57 58 return false; 59 } 60 61 int main() 62 { 63 cin >> n >> m; 64 for (int i = 1; i <= n; i ++ ) cin >> wf[i]; 65 66 memset(h, -1, sizeof h); 67 for (int j = 0; j < m; j ++ ) 68 { 69 int a, b, c; 70 cin >> a >> b >> c; 71 add(a, b, c); 72 } 73 74 double l = 0, r = 1e6; 75 while (r - l > 1e-4) 76 { 77 double mid = (l + r) / 2; 78 if (check(mid)) l = mid; 79 else r = mid; 80 } 81 82 printf("%.2lf\n", l); 83 84 return 0; 85 }
https://www.acwing.com/problem/content/1167/
acwing 1165.单词环

该题共三个点取药考虑:
①如何去建图
②01分数规划求最值
③因为数据很大,需要考虑一些优化
建图:如果直接用字符串的最后两个位去建图,一共26个字母,两位有676中选择,单词数量是$10^5$个,极端情况下,即所有字符串的字母全为相同,那么边数可到达$10^{10}$条边,时间空间都不允许。这时候我们要采取一种对偶的思想,即一个字符串$ababc$,我们将其看为是$ab$这个点连向$bc$这个点, 长度是5。这样边数就只有$10^5$条,而点数也变为676个。点变为边,边变为点,新的图和原来的图是等价的。
求最值:如上题的01分数规划求解。二分的范围是0 - 1000,0是取不到的。(每个字词都是1000的长度就是最大值1000)。
$\frac{\sum_{i = 1}^{n}w_{i}}{\sum1} > Mid$
$\sum_{i = 1}^{n}w_{i} > Mid \sum1 \leftrightarrow \sum_{i = 1}^{n}w_{i} - Mid \sum1 > 0$
$\sum_{i = 1}^{n}(w_{i} - Mid*1) > 0$,当$Mid$取0的时候没有正环,那么当$Mid$大于0的时候更不可能有正环。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 6 using namespace std; 7 8 const int N = 700, M = 100010; 9 10 int n; 11 int h[N], e[M], w[M], ne[M], idx; 12 double dist[N]; 13 int q[N], cnt[N]; 14 bool st[N]; 15 16 void add(int a, int b, int c) 17 { 18 e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; 19 } 20 21 bool check(double mid) 22 { 23 memset(st, 0, sizeof st); 24 memset(cnt, 0, sizeof cnt); 25 26 int hh = 0, tt = 0; 27 for (int i = 0; i < 676; i ++ ) 28 { 29 q[tt ++ ] = i; 30 st[i] = true; 31 } 32 33 int count = 0; 34 while (hh != tt) 35 { 36 int t = q[hh ++ ]; 37 if (hh == N) hh = 0; 38 st[t] = false; 39 40 for (int i = h[t]; ~i; i = ne[i]) 41 { 42 int j = e[i]; 43 if (dist[j] < dist[t] + w[i] - mid) 44 { 45 dist[j] = dist[t] + w[i] - mid; 46 cnt[j] = cnt[t] + 1; 47 if ( ++ count > 10000) return true; // 经验上的trick 48 if (cnt[j] >= N) return true; 49 if (!st[j]) 50 { 51 q[tt ++ ] = j; 52 if (tt == N) tt = 0; 53 st[j] = true; 54 } 55 } 56 } 57 } 58 59 return false; 60 } 61 62 int main() 63 { 64 char str[1010]; 65 while (scanf("%d", &n), n) 66 { 67 memset(h, -1, sizeof h); 68 idx = 0; 69 for (int i = 0; i < n; i ++ ) 70 { 71 scanf("%s", str); 72 int len = strlen(str); 73 if (len >= 2) 74 { 75 int left = (str[0] - 'a') * 26 + str[1] - 'a'; 76 int right = (str[len - 2] - 'a') * 26 + str[len - 1] - 'a';//变为26进制数 77 add(left, right, len); 78 } 79 } 80 81 if (!check(0)) puts("No solution"); 82 else 83 { 84 double l = 0, r = 1000; 85 while (r - l > 1e-4) 86 { 87 double mid = (l + r) / 2; 88 if (check(mid)) l = mid; 89 else r = mid; 90 } 91 92 printf("%lf\n", r); 93 } 94 } 95 96 return 0; 97 }

浙公网安备 33010602011771号