此处有
目录↑
描述
农夫布朗的牧场上的篱笆已经失去控制了。它们分成了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),无法由本解法得出