泡沫

博客园 首页 联系 订阅 管理

最小环问题:求个图中环路径代价最小的回路。

如何求最小环?假如有 路径1->3->2,如果此时已经知道2-1的最短路径就好了。 回想下floyed的更新过程,就会发现更新第k次时,比k小的点之间都是最短距离的(要是点是联通的话)。所以给出解法:第k次更新图时,枚举和k相连的两条边。如 环路代价 = dist[i][k] + dist[k][j] + dist[j][i];

求无向图中最小环的个数,先算出一个最小环代价,把之后算出的环代价与之对比更新就可以了。这个图中是求不同的最小环的个数,所以重边是没有影响的

fzu 2090  http://acm.fzu.edu.cn/problem.php?pid=2090

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <algorithm>
 6 using namespace std;
 7 
 8 #define maxn 108
 9 #define INF 1<<24
10 int dist[maxn][maxn], g[maxn][maxn];
11 int n, m;
12 
13 void init(){
14     for ( int i=1; i<=n; ++i )
15        for ( int j=1; j<=n; ++j )
16          dist[i][j] = g[i][j] = INF;
17     int u, v, w;
18     for ( int i=0; i<m; ++i ) {
19          scanf("%d%d%d", &u, &v, &w);
20          if(dist[u][v] > w) {
21              dist[u][v] = dist[v][u] = w;
22              g[u][v] = g[v][u] = w;
23          }
24     }
25 };
26 
27 void solve(){
28     int minn = INF, cnt=0;
29     for ( int k=1; k<=n; ++k ){
30         // 枚举与k相连的两条边,且端点号是小于k的
31          for ( int i=1; i<k; ++i ) if(g[i][k]^INF)
32           for( int j=i+1; j<k; ++j ) if(dist[i][j]^INF && g[k][j]^INF)
33           {
34               int tmp = dist[i][j]+g[i][k]+g[k][j];
35               if(tmp < minn ) {// 比最小的还小就更新
36                     minn = tmp;  cnt=1;
37               }else if(tmp == minn) ++cnt; // 和当前最小的环代价相等
38           }
39    
40       // 更新最短路
41         for(int i=1; i<=n; ++i ) if(dist[i][k]^INF)
42           for(int j=1; j<=n; ++j ) if(dist[k][j]^INF)
43           {
44               int tmp= dist[i][k]+dist[k][j];
45               if(tmp < dist[i][j]) dist[i][j] = tmp;
46           }
47     }
48     if(cnt > 0) printf("%d %d\n", minn, cnt);
49     else puts("-1 -1");
50 };
51 
52 int main(){
53     int  T; scanf("%d", &T);
54     while( T-- ) {
55          scanf("%d%d", &n, &m);
56          init();
57          solve();
58     }
59 };

求最小环路径,任意一条最小环的路径。

poj 1734 http://poj.org/problem?id=1734

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <vector>
 6 #include <algorithm>
 7 using namespace std;
 8 
 9 #define maxn 108
10 int n, m;
11 int dist[maxn][maxn], g[maxn][maxn];
12 int pre[maxn][maxn]; // 路径
13 #define INF 1<<24
14 vector<int > ans;
15 
16 void init(){
17     for(int i=1; i<=n; ++i)
18       for(int j=1; j<=n; ++j)
19       dist[i][j]=g[i][j]=INF, pre[i][j]=i;
20     int u, v, w;
21     for(int i=0; i<m; ++i ) {
22         scanf("%d%d%d", &u, &v, &w);
23         if(w < dist[u][v] ){
24            dist[u][v] = dist[v][u] = w;
25            g[u][v] = g[v][u] = w;
26         }
27     }
28 };
29 
30 
31 void solve(){
32     int minn = INF;  ans.clear();
33     for(int k=1; k<=n; ++k )
34     {
35        // 枚举两条边,点号小于k
36         for(int i=1; i<k; ++i) if(g[i][k]^INF)
37           for(int j=i+1; j<k; ++j) if(g[k][j]^INF && dist[i][j]^INF) {
38               int tmp = dist[i][j]+g[i][k]+g[k][j];
39               if(tmp < minn) { // 求出路径
40                    minn = tmp;  ans.clear();
41                    int p = j;
42                    while(p != i) {
43                         ans.push_back(p);
44                         p = pre[i][p];
45                    }
46                    ans.push_back(i);
47                    ans.push_back(k);
48               }
49           }
50 
51         for(int i=1; i<=n; ++i ) if(dist[i][k]^INF)
52           for(int j=1; j<=n; ++j )if(dist[k][j]^INF) {
53               int tmp = dist[i][k]+dist[k][j];
54               if(tmp < dist[i][j]) {
55                   dist[i][j]= tmp;
56                   pre[i][j] = pre[k][j]; // 从后往前
57               }
58           }
59     }
60 
61     if(minn ^ INF) {
62         //cout<< minn << endl;
63          for(int i=0; i<ans.size(); ++i){
64            if(i) printf(" ");  printf("%d", ans[i]);
65          }
66          puts("");
67          return ;
68     }
69     puts("No solution.");
70 };
71 
72 int main(){
73     while(~scanf("%d%d", &n, &m)){
74       init();
75       solve();
76     }
77 };

 

最大环路问题和最小环路是相同的吧(没遇到过求最大环的);

求最大平均环代价。 知道spfa能够判断环中是否环(正环和负环都是可以的)。开始图中的都是正环(假如有环的话)。对每条边减去一个值,再判断图中是否有正环存在,恰好没有就表明和这个值就是最大的平均环的代价,因为环中x条边都减去y后,恰好会使得整个环不是正环,这个y值就是要求的最大平均环代价了;

poj 2949 http://poj.org/problem?id=2949

求解时只需二分枚举y即可,注意精度;

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int maxm = 100005;
 8 const int maxn = 27*27;
 9 int n, head[maxn], e, N;
10 struct Edge{
11     int u, v, next; double w;
12     Edge(){}
13     Edge(int U, int V, int Ne, double W):
14          u(U), v(V), next(Ne), w(W){}
15 }edge[maxm];
16 
17 void add(int u, int v, double w){
18    edge[e] = Edge(u, v, head[u], w); head[u] = e++;
19 }
20 
21 int mm[30][30];
22 
23 int get( char x, char y) {
24     int i = x-'a', j = y-'a';
25     if(mm[i][j] == 0) mm[i][j]=++N;
26     return mm[i][j];
27 }
28 
29 double maxLength;
30 
31 void init(){
32      char str[1071];
33      e = 0;   N = 0; maxLength = 0; //maxLength 最大的边
34      memset(head, -1, sizeof head); //开始这里我用的是fill,悲剧的rt了如干次啊
35      memset(mm, 0, sizeof mm);
36 
37      getchar();
38      for(int i=0; i<n; ++i ) {
39          scanf("%s", str);
40          int sz = strlen(str);
41          if(sz < 3) continue;
42          int u = get(str[0], str[1]);
43          int v = get(str[sz-2], str[sz-1]);
44          add(u, v, sz); //每个字串见条边就可以了
45          if(sz > maxLength) maxLength = sz;
46      }
47 }
48 
49 double dist[maxn];
50 int cnt[maxn];
51 bool vis[maxn];
52 int Q[maxn];
53 
54 bool spfa( double x ) {
55     fill(dist+1, dist+1+N, 0);
56     fill(cnt+1, cnt+1+N, 0 );
57     memset(vis, 0, sizeof vis);
58     int l=0, r=0;
59     for(int i=1; i<=N; ++i)
60       Q[r++] = i, vis[i]=1;
61     while(l != r) {
62          int u = Q[l++]; if(l == maxn) l=0;  vis[u]=0;
63          for ( int i=head[u]; ~i; i=edge[i].next){
64              int v = edge[i].v; double w =  edge[i].w;
65              if(dist[u]+w-x > dist[v]){
66                   dist[v] = dist[u]+w-x;
67                   if(vis[v] ) continue;
68                   Q[r++] = v; vis[v]=1;
69                   if(r == maxn) r=0;
70                   if(++cnt[v] > N) return 1; // 存在正环
71              }
72          }
73     }
74     return 0;
75 }
76 
77 #define eps 1e-4
78 void solve()
79 {
80     double l = 0, r = maxLength, mid, ans=-1;
81     while(r-l >= eps){// 二分球结果
82          mid = (l+r)/2.0;
83          if(spfa( mid )  ) {
84               ans = mid;
85               l = mid;
86          }else r = mid;
87     }
88     if(ans > eps) printf("%.2lf\n", ans);
89     else puts("No solution.");
90 };
91 
92 
93 int main(){
94      while( scanf("%d", &n), n ){
95          init();
96          solve();
97      }
98 }

 

求简单无向图中环的个数?简单无向图即 无自环,无重边的无向图。

 这个是NP问题; codeforces 上有一题用的是状态压缩写的。

 dp[s][i] s集合里最小的点到其他点的路径数;

dp[s][i] += dp[s^(1<<i)][j](g[j][i]=true)

ans加上可以构成环的路径数.怎么才能构成环呢? 如a->b->.....->c ,如果知道ac是可达的,只要加上a,经过ab...到达c的路径数就可以了。注意a是这个集合里最小的数。而且同一个环会被记录两次,因为2条路径才是一个环。 

codeforces 11D http://www.codeforces.com/contest/11/problem/D

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 typedef __int64 ll;
 8 #define maxn 20
 9 ll dp[1<<maxn][maxn]; // 注意数据
10 bool g[maxn][maxn];
11 // dp[s][i] s中最小的点到其他点路径数;
12 // dp[s][i] += dp[s^i][j](g[i][j] = true )
13 
14 int main(){
15     int n, m;
16     while( cin >> n >> m ) {
17         memset(g, 0, sizeof g);
18         int state = (1<<n);
19         for ( int i=0; i<state; ++i)
20           for ( int j=0; j<n; ++j )
21             dp[i][j] = 0;
22 
23         for ( int i=0, a, b; i<m; ++i ){
24             scanf ("%d%d", &a, &b);
25             --a, --b;
26             g[a][b] = g[b][a] = true;
27             dp[(1<<a)|(1<<b)][a] = dp[(1<<a)|(1<<b)][b] = 1;
28         }
29 
30         ll ans = 0;
31         for ( int s=1; s<state; ++s ){
32              int i, j, k;
33              for ( i=0; i<n && !(s&(1<<i)); ++i );
34              for ( j=i+1; j<n; ++j ) if(s&(1<<j) )
35              {
36                  for ( k=i+1; k<n; ++k ) if(s&(1<<k)){
37                       if(g[k][j] )
38                         dp[s][j] += dp[s^(1<<j)][k];
39                  }
40                  if(g[i][j] && (s^(1<<i)^(1<<j))) // 3个点以上才行
41                    ans += dp[s][j];
42              }
43         }
44         // 枚举了环的两侧,so。。。
45         cout << (ans>>1) << endl;
46     }
47 };

 

 

posted on 2013-04-19 21:33  木-天空  阅读(3049)  评论(1编辑  收藏  举报