引水工程
时间限制: 3 Sec 内存限制: 128 MB提交: 11 解决: 6
[提交][状态][讨论版]
题目描述
南水北调工程是优化水资源配置、促进区域协调发展的基础性工程,是新中国成立以来投资额最大、涉及面最广的战略性工程,事关中华民族长远发展。“南水北调工程”,旨在缓解中国华北和西北地区水资源短缺的国家战略性工程。就是把中国长江流域丰盈的水资源抽调一部分送到华北和西北地区。我国南涝北旱,南水北调工程通过跨流域的水资源合理配置,促进南北方经济、社会与人口、资源、环境的协调发展。
整个工程分东线、中线、西线三条调水线。东线工程位于东部,因地势低需抽水北送至华北地区。中线工程从汉水与其最大支流丹江交汇处的丹江口水库引水,自流供水给黄淮海平原大部分地区,20多座大中城市;西线工程在青藏高原上,由长江上游向黄河上游补水。
现在有N个区域需要建设水资源工程,它们可以自建水库解决缺水问题,也可以从已有水源的地区建立管道引水过来。当然,这些建设都需要大量投资。
你能不能给出一个优化水资源配置方案,在保证每个区域都能用上水的前提下,使得整个引水工程费用最低。
输入
第一行: K 表示有多少组测试数据。
接下来对每组测试数据:
第1行: N 表示有N个区域( 1<=N<=300 )
第2 行: W1 W2 …. WN Wi表示第i个区域自建水库需要的费用
再有N行: Pi1 Pi2 …. Pin Pij表示建立第i个区域与第j个区域引水管道的费用
输出
对于每组测试数据,输出占一行,即建立整个引水工程的最小费用。
样例输入
1
5
5 4 4 3 6
0 2 2 2 2
2 0 3 3 3
2 3 0 4 5
2 3 4 0 1
2 3 5 1 0
样例输出
10
这道题的思路(我觉得有漏洞):在全部都自己建水的基础上,选最便宜的点建水,然后借这个点向外松弛。
虽然我AC了,但是发现有一组我自己编的样例过不了,那就是
1
3
11 11 10
0 1 1
1 0 1
10 10 0
答案应该是12,但是我的代码是21,网上很多代码都是21,这其实源于 选最便宜的点建水 这一步有问题,因此我觉得应该每个点都作为起点试一下,但TLM了。
真正的解法应该用克鲁斯卡尔。
下面是AC代码(因为题目不严谨而AC)prim解法
#include <iostream> #include <string> #include <cstring> #include <algorithm> #define inf 0x3f3f3f3f using namespace std; int a[305]; int e[305][305]; bool v[305]; int n; int prim() { memset(v, 0, sizeof(v)); int sum = 0; while (1) { int mbu = inf; int k, i; k = -1; for (i = 1; i <= n; i++)//先都建水,找出当前建水费用最少的点 { if (v[i]==0&&(k==-1||a[i] < a[k])) { k = i; } } if (k == -1) break; sum = sum + a[k]; v[k] = 1;//模拟删除改点 for (i = 1; i <= n; i++) { a[i] = min(a[i], e[k][i]);//用该点松弛 } } return sum; } int main() { int t; cin >> t; while (t--) { cin >> n; memset(e, inf, sizeof(e)); int i; for (i = 1; i <= n; i++) { cin >> a[i]; } int j; for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) { cin >> e[i][j]; } } cout << prim() << endl; } }
下面是克鲁斯卡尔(Kruskal)解法,我的样例也能过
巧妙地把在i建水设为e[0][i]
思路,将自建水库 i 需要的费用转化为边0---->i . 然后用并查集算法求点 0到n的最小生成树即可。 代码: #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; struct Edge { int u; int v; int cost; }e[90010]; int map[310][310],c[310],n,p[310]; int comp(Edge e1,Edge e2) { return e1.cost<e2.cost; } int find(int x) //找父节点 { if(p[x]!=x) { p[x]=find(p[x]); } return p[x]; } bool bin(int x,int y) //将x,y添加至一个集合 { int g,h; g=find(x); h=find(y); if(g==h) return false; p[g]=h; return true; } bool judge() //判断是否所有点都在一个集合 { int i,count=0; for(i=0;i<=n;i++) { if(p[i]==i) count++; if(count>1) return false; } return true; } int main() { int t,k,i,j,x,res; scanf("%d",&t); while(t--) { res=0; k=0; scanf("%d",&n); for(i=0;i<=n;i++) p[i]=i; for(i=1;i<=n;i++) { scanf("%d",&x); e[k].u=0; e[k].v=i; e[k++].cost=x; } for(i=1;i<=n;i++) for(j=1;j<=n;j++) { scanf("%d",&x); e[k].u=i; e[k].v=j; e[k++].cost=x; } sort(e,e+k,comp); for(i=0;i<k;i++) { if(bin(e[i].u,e[i].v)) res+=e[i].cost; if(judge()) break; } printf("%d ",res); } return 0; }
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> #include<set> using namespace std; #define mem(x,y) memset(x,y,sizeof(x)) #define SI(x) scanf_s("%d",&x) #define SL(x) scanf_s("%lld",&x) #define PI(x) printf("%d",x) #define PL(x) printf("%lld",x) #define P_ printf(" ") const int INF = 0x3f3f3f3f; const double PI = acos(-1.0); typedef long long LL; const int MAXN = 350; struct Node { int u, v, w; void init(int x = 0, int y = 0, int z = 0)/*:u(x),v(y),w(z)*/ { u = x; v = y; w = z; } friend bool operator < (Node a, Node b) { return a.w<b.w; } }dt[1010]; int ans; int pre[MAXN]; int find(int x) { return pre[x] = x == pre[x] ? x : find(pre[x]); } void merge(Node a) { int f1 = find(a.u), f2 = find(a.v); if (f1 != f2)pre[f1] = f2, ans += a.w; } int main() { int K, N; SI(K); while (K--) { SI(N); int x, k = 0; for (int i = 0; i <= N; i++) for (int j = 1; j <= N; j++) { SI(x); if (i<j) { dt[k++].init(i, j, x); } } sort(dt, dt + k); //for(int i=0;i<k;i++)printf("%d %d %d ",dt[i].u,dt[i].v,dt[i].w); for (int i = 0; i <= N; i++)pre[i] = i; ans = 0; for (int i = 0; i<k; i++)merge(dt[i]); printf("%d ", ans); } return 0; }
下面是能过这组样例但是超时的代码
#include <iostream> #include <string> #include <cstring> #include <algorithm> #define inf 0x3f3f3f3f using namespace std; int a[305]; int e[305][305]; bool v[305]; int u[305]; int n; int prim() { int sum = 0; while (1) { int mbu = inf; int k, i; k = -1; for (i = 1; i <= n; i++)//先都建水,找出当前建水费用最少的点 { if (v[i]==0&&(k==-1||a[i] < a[k])) { k = i; } } if (k == -1) break; sum = sum + a[k]; v[k] = 1;//模拟删除改点 for (i = 1; i <= n; i++) { a[i] = min(a[i], e[k][i]);//用该点松弛 } } return sum; } int main() { int t; cin >> t; while (t--) { cin >> n; memset(e, inf, sizeof(e)); int i; for (i = 1; i <= n; i++) { cin >> u[i]; } int j; for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) { cin >> e[i][j]; } } int mm = inf; int s1; memset(v, 0, sizeof(v)); for (i = 1; i <= n; i++) a[i] = u[i]; s1 = prim(); if (s1 < mm) mm = s1; for (i = 1; i <= n; i++)//我加了这一步 { int j; for (j = 1; j <= n; j++) a[j] = u[j]; memset(v, 0, sizeof(v)); v[i] = 1;//这一点确定不自己建水 s1 = prim(); if (s1 < mm) mm = s1; v[i] = 0;//回退 } cout << mm << endl; } } //1 3 //10 10 11 //0 1 1 //1 0 1 //10 10 0 //
@太年轻
感谢这位游客,告诉我了不能过我的样例的真相:
其实这题没有说明白,但是观察样例可以
看出每个 i 到 j 的值与 j 到 i 的值是相等的,题目中没有明说这一点,
但是那样写都过了说明测试数据没有不相等的情况。
其实克鲁斯卡尔和Prim都是相通的,能用克鲁斯卡尔就一定能用Prim.
比如这题,我们将错就错,题目没有说管道是单向的,那么拿博主造的
样例来说,在连边的时候较长的边就废弃不用了,用Prim时只需在
连边的时候加一句判断条件即可,这样博主的样例Prim也能过了,
至于克鲁斯卡尔为什么能过,以为sort()排序已经把长的边排到了
后面,实际上长边根本没有用到,和Prim前面的剔除长边道理相通。
话说回来,如果道路是单向的,那么Prim就不能剔除边,但这时用
克鲁斯卡尔也是错的,因为克鲁斯卡尔不会考虑方向,比如存在两条边
a-->b c-->b 若用克鲁斯卡尔加入并查集,则a,c便在一个并查集里,
但实际上a,c是不相通的。