负环

求负环的常用方法,基于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 }

 

posted @ 2020-03-23 19:53  dzcixy  阅读(248)  评论(1)    收藏  举报