【最小生成树】新的开始(newstart) 解题报告
【题目描述】
发展采矿业当然首先得有矿井, 小FF花了上次探险获得的千分之一的财富请人在岛上挖了n口矿井, 但他似乎忘记考虑的矿井供电问题…… 为了保证电力的供应, 小FF想到了两种办法:
- 在这一口矿井上建立一个发电站,费用为v(发电站的输出功率可以供给任意多个矿井)。
- 将这口矿井与另外的已经有电力供应的矿井之间建立电网, 费用为p。小FF希望身为”NewBe_One" 计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。
【输入格式】
第一行一个整数n, 表示矿井总数。 第2~n+1行,每行一个整数, 第i个数v[i]表示在第i口矿井上建立发电站的费用。 接下来为一个n*n的矩阵P, 其中p[ i , j ]表示在第i口矿井和第j口矿井之间建立电网的费用(数据保证有p[ i, j ] = p[ j, i ], 且 p[ i, i ]=0)。
【输出格式】仅一个整数, 表示让所有矿井获得充足电能的最小花费。
【输入样例】
4 5 4 4 3 0 2 2 2 2 0 3 3 2 3 0 4 2 3 4 0
【输出样例】
9
输出样例说明: 小FF可以选择在4号矿井建立发电站然后把所有矿井都与其建立电网,总花费是 3+2+2+2 =9。
【数据范围】
对于30%的数据: 1<=n<=50; 对于100%的数据: 1<=n<=300; 0<=v[i], p[i,j] <=10^5.
分析:
第一眼看上去是一个MST(最小生成树),唯一不同的是题中有一个发电站的概念,如果将它与MST的问题分隔开,先求MST再来选择价值最小的发电站的话(即贪心思路),是不能保证求出最优解的,因为这样就默认了每个矿井都必须是连通的,当边权特别大,不取这条边,分别修两个电站才是最优的。因此贪心的方案是错的,没有把题中两个关键的元素联系起来。正确的解法是在原图的基础上虚拟一个点,原图的每一个节点都与这个虚拟点有一条边,边权为在该点上修建电站的花费。从而得到一个新图,对新图的求MST,结果结果就是MST的边权和。这是一种模型转换的思路,十分值得借鉴。
贪心的思路没有将题目整体考虑,所以要得到正确的解题模型,整体考虑题目往往非常重要。
贪心思路是我在考试时想的错误方法,编程复杂不说还WA了。
下面修改过后用模型转换思路得到的AC代码:
/* ID: ringxu97 LANG: C++ TASK: Newstart SOLUTION: 生成树 */ #include<cstdio> #include<cstring> #include<iostream> #include<cmath> #include<cstdlib> #include<algorithm> #include<vector> #include<stack> #include<queue> using namespace std; const int maxn=300+10; const int inf=0x3f3f3f3f; int n; struct DisjointSet//并查集 { int n; int fa[maxn]; DisjointSet(const int &k):n(k) { for(int i=1;i<=k;++i) fa[i]=i; } int Get(int a) { if(fa[a]==a)return a; else return fa[a]=Get(fa[a]); } void Union(int a,int b) { fa[Get(a)]=Get(b); } bool Same(int a,int b) { return Get(a)==Get(b); } }; struct EDGE { int u,v,w; }E[maxn*maxn/2];//边表 inline bool cmp(EDGE a,EDGE b) { return a.w<b.w; } EDGE *p=E; void addedge(int u,int v,int w)//向边表中加边 { p->u=u; p->v=v; (p++)->w=w; } int price[maxn]; int G[maxn][maxn]; int ans=0; void read()//读入数据 { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d",price+i); } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) { int tmp; scanf("%d",&tmp); if(j>i) { addedge(i,j,tmp);//无向边 } } for(int i=1;i<=n;++i) { addedge(i,n+1,price[i]);//虚拟点为n+1,与原图各点连边 } } void Kruskal()//求MST { memset(G,0,sizeof(G)); DisjointSet ds(n+1); sort(E,p,cmp); for(EDGE *i=E;i<p;++i) { if(!ds.Same(i->u,i->v)) { ans+=i->w; ds.Union(i->u,i->v); } } } int main() { read(); Kruskal(); printf("%d\n",ans); return 0; }