USACO-Section 4.1 Fence Loops (无向图最小环[Dijkstra||Floyd])

此处有
目录↑

描述

农夫布朗的牧场上的篱笆已经失去控制了。它们分成了1~200英尺长的线段。只有在线段的端点处才能连接两个线段,有时给定的一个端点上会有两个以上的篱笆。结果篱笆形成了一张网分割了布朗的牧场。布朗想将牧场恢复原样,出于这个考虑,他首先得知道牧场上哪一块区域的周长最小。 布朗将他的每段篱笆从1到N进行了标号(N=线段的总数)。他知道每段篱笆有如下属性:

  • 该段篱笆的长度
  • 该段篱笆的一端所连接的另一段篱笆的标号
  • 该段篱笆的另一端所连接的另一段篱笆的标号

幸运的是,没有篱笆连接它自身。对于一组有关篱笆如何分割牧场的数据,写一个程序来计算出所有分割出的区域中最小的周长。

例如,标号1~10的篱笆由下图的形式组成(下面的数字是篱笆的标号):

         1
 +---------------+
 |\             /|
2| \7          / |
 |  \         /  |
 +---+       /   |6
 | 8  \     /10  |
3|     \9  /     |
 |      \ /      |
 +-------+-------+
     4       5

上图中周长最小的区域是由2,7,8号篱笆形成的。

格式

PROGRAM NAME: fence6

INPUT FORMAT:

(file fence6.in)

第1行: N (1 <= N <= 100)

第2行到第3*N+1行: 每三行为一组,共N组信息:

每组信息的第1行有4个整数: s, 这段篱笆的标号(1 <= s <= N); Ls, 这段篱笆的长度 (1 <= Ls <= 255); N1s (1 <= N1s <= 8) 与本段篱笆的一端 所相邻的篱笆的数量; N2s与本段篱笆的另一端所相邻的篱笆的数量。 (1 <= N2s <= 8).

每组信息的的第2行有 N1s个整数, 分别描述与本段篱笆的一端所相邻的篱笆的标号。

每组信息的的第3行有N2s个整数, 分别描述与本段篱笆的另一端所相邻的篱笆的标号。

OUTPUT FORMAT:

(file fence6.out)

输出的内容为单独的一行,用一个整数来表示最小的周长。

SAMPLE INPUT

10
1 16 2 2
2 7
10 6
2 3 2 2
1 7
8 3
3 3 2 1
8 2
4
4 8 1 3
3
9 10 5
5 8 3 1
9 10 4
6
6 6 1 2 
5 
1 10
7 5 2 2 
1 2
8 9
8 4 2 2
2 3
7 9
9 5 2 3
7 8
4 5 10
10 10 2 3
1 6
4 9 5

SAMPLE OUTPUT

12

解法一:Dijkstra – O(n^3)

明白题意后想了一个解法:以每一个顶点做起点,每次去掉与其相连的一条边,然后求该点到这条边另一点的最短路,再加上这条边的权值就可得这个环的权值,每次取最小即可,复杂度为:O(n^4)

看了题解后发现,本题是求无向图的最小环,解法为:枚举每一条边,去掉该边,然后求该边两点的最短路,再加上这条边的权值就可得到这个环的权值,最小权值即为答案,复杂度为:O(n^3),但是若本图为稠密图时,复杂度仍为O(n^4),n表示顶点个数

写代码的时候才发现本题的难点不是求最小环,而是如何将边的信息转化为点的信息,我用了map以判断某一边的点的下标

可以证明本题点的个数不会超过边的个数,新边加入时,为了增加新点,必定不会和其他两条边共同一个顶点,即最终图一个顶点只有两条边

/*
ID: your_id_here
PROG: fence6
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>

using namespace std;

struct Node {
    int s[9];//s数组表示包括本端所连的fence

    Node() {
        memset(s,0,sizeof(s));
    }

    bool operator < (const Node& a) const {
        for(int i=0;i<9;++i)
            if(s[i]<a.s[i])
                return true;
            else if(s[i]>a.s[i])
                return false;
        return false;
    }

    bool operator ==(const Node& a) const {
        for(int i=0;i<9;++i)
            if(s[i]!=a.s[i])
                return false;
        return true;
    }
}fence[205];

int n,s,ls,ns,n1s,n2s,sta,des,cur;
int g[105][105],cnt=0,dis[105],tmp,ans;
bool vis[105];
map<Node,int> mp;

int dijkstra() {
    memset(vis,false,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    dis[sta]=0;
    for(int i=1;i<cnt;++i) {
        int index=0;
        for(int j=1;j<=cnt;++j)
            if(!vis[j]&&dis[j]<dis[index])
                index=j;
        if(index==0)
            return 0x3f3f3f3f;
        if(index==des)//返回sta与des的最短距离
            return dis[index];
        vis[index]=true;
        for(int j=1;j<=cnt;++j)
            if(!vis[j]&&dis[j]>dis[index]+g[index][j])
                dis[j]=dis[index]+g[index][j];
    }
    return dis[des];
}

int main() {
    freopen("fence6.in","r",stdin);
    freopen("fence6.out","w",stdout);

    memset(g,0x3f,sizeof(g));
    scanf("%d",&n);
    for(int i=1;i<=n;++i) {//读入边数据,并给每个点标一个数
        scanf("%d%d%d%d",&s,&ls,&n1s,&n2s);
        fence[i<<1].s[8]=fence[(i<<1)|1].s[8]=s;

        while(n1s-->0)
            scanf("%d",&fence[i<<1].s[n1s]);
        sort(fence[i<<1].s,fence[i<<1].s+9);
        if(mp[fence[i<<1]]==0)
            mp[fence[i<<1]]=++cnt;

        while(n2s-->0)
            scanf("%d",&fence[(i<<1)|1].s[n2s]);
        sort(fence[(i<<1)|1].s,fence[(i<<1)|1].s+9);
        if(mp[fence[(i<<1)|1]]==0)
            mp[fence[(i<<1)|1]]=++cnt;

        sta=mp[fence[i<<1]];
        des=mp[fence[(i<<1)|1]];
        g[sta][des]=g[des][sta]=ls;//边信息转成点信息
    }

    n=(n<<1)|1;
    ans=0x3f3f3f3f;
    for(int i=2;i<n;i+=2) {
        sta=mp[fence[i]];
        des=mp[fence[i+1]];
        tmp=g[sta][des];
        g[sta][des]=g[des][sta]=0x3f3f3f3f;//先去掉这条边
        ans=min(ans,tmp+dijkstra());
        g[sta][des]=g[des][sta]=tmp;//复原掉这条边
    }
    printf("%d\n",ans);
    return 0;
}

解法二:Floyd – O(n^3)

在网上看到无向图最小环应该用Floyd求解,非常简洁

大致思路:无向图的环最少有三个点,所以需要增加一部分求最小环;枚举中间点k,在用其更新最短路前,先找最小环,令1<=i<j<k,即k点必定不在i,j的最短路上,则这个环中至少有三个点,这个环的权值为:dis[i][j]+g[i][k]+g[k][j]

/*
ID: your_id_here
PROG: fence6
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>

using namespace std;

struct Node {
    int s[9];//s数组表示包括本端所连的fence

    Node() {
        memset(s,0,sizeof(s));
    }

    bool operator < (const Node& a) const {
        for(int i=0;i<9;++i)
            if(s[i]<a.s[i])
                return true;
            else if(s[i]>a.s[i])
                return false;
        return false;
    }

    bool operator ==(const Node& a) const {
        for(int i=0;i<9;++i)
            if(s[i]!=a.s[i])
                return false;
        return true;
    }
}fence[205];

int n,s,ls,ns,n1s,n2s,sta,des,cur;
int g[105][105],cnt=0,dis[105][105];
bool vis[105];
map<Node,int> mp;

int floyd() {
    int ans=0x1f1f1f1f;
    for(int i=1;i<=n;++i)
        for(int j=i;j<=n;++j)
            dis[i][j]=dis[j][i]=g[i][j];

    for(int k=1;k<=cnt;++k) {
        for(int i=1;i<k;++i)//寻找最小环
            for(int j=i+1;j<k;++j)
                if(dis[i][j]+g[i][k]+g[k][j]<ans)//由于此处会存在三个INF相加,所以INF设为0x1f1f1f1f
                    ans=dis[i][j]+g[i][k]+g[k][j];

        for(int i=1;i<=n;++i)//更新最短路
            for(int j=1;j<=n;++j)
                if(dis[i][j]>dis[i][k]+dis[k][j])
                    dis[i][j]=dis[i][k]+dis[k][j];
    }
    return ans;
}

int main() {
    freopen("fence6.in","r",stdin);
    freopen("fence6.out","w",stdout);

    memset(g,0x1f,sizeof(g));
    scanf("%d",&n);
    for(int i=1;i<=n;++i) {//读入边数据,并给每个点标一个数
        scanf("%d%d%d%d",&s,&ls,&n1s,&n2s);
        fence[i<<1].s[8]=fence[(i<<1)|1].s[8]=s;

        while(n1s-->0)
            scanf("%d",&fence[i<<1].s[n1s]);
        sort(fence[i<<1].s,fence[i<<1].s+9);
        if(mp[fence[i<<1]]==0)
            mp[fence[i<<1]]=++cnt;

        while(n2s-->0)
            scanf("%d",&fence[(i<<1)|1].s[n2s]);
        sort(fence[(i<<1)|1].s,fence[(i<<1)|1].s+9);
        if(mp[fence[(i<<1)|1]]==0)
            mp[fence[(i<<1)|1]]=++cnt;

        sta=mp[fence[i<<1]];
        des=mp[fence[(i<<1)|1]];
        g[sta][des]=g[des][sta]=ls;//边信息转成点信息
    }

    printf("%d\n",floyd());
    return 0;
}

解法三:Prim – O(n^2)

大致思路是:求出最小生成树,再求出生成树上任意两点的距离,最后枚举不在生成树上的边,即可得到所有环的权值,取最小即可

但是这种很快解法网上竟然搜不到,于是便对其正确性产生了怀疑,看到后面有一组反例(如果本题fence符合实际,则无此反例):括号内为长度

  -  fence1(1)
 / \ fence2(1),fence3(1)
 --- fence4(3)
/    \ fence5(1),fence6(1)
------fence7(6)
\____/fence 8(1),fence9(1),fence10(1)

可以发现,存在一种最小生成树为:

  -         fence1(1)
 /          fence2(1)
/    \      fence5(1),fence6(1)
\____/      fence8(1),fence9(1),fence10(1)

但最小环应为:fence1(1),fence2(1),fence4(3),fence3(1),无法由本解法得出

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/idealism_xxm/article/details/51156226
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞