zoukankan      html  css  js  c++  java
  • zufe oj 引水工程( 巧妙地把在i建水设为e[0][i])

    引水工程

    时间限制: 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;
        }
    }
    View Code

    下面是克鲁斯卡尔(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;
    }
    View Code
    #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;
    }
    View Code
    下面是能过这组样例但是超时的代码
    #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
    //
    View Code

    @太年轻

    感谢这位游客,告诉我了不能过我的样例的真相:

    其实这题没有说明白,但是观察样例可以
    看出每个 i 到 j 的值与 j 到 i 的值是相等的,题目中没有明说这一点,
    但是那样写都过了说明测试数据没有不相等的情况。
    其实克鲁斯卡尔和Prim都是相通的,能用克鲁斯卡尔就一定能用Prim.
    比如这题,我们将错就错,题目没有说管道是单向的,那么拿博主造的
    样例来说,在连边的时候较长的边就废弃不用了,用Prim时只需在
    连边的时候加一句判断条件即可,这样博主的样例Prim也能过了,
    至于克鲁斯卡尔为什么能过,以为sort()排序已经把长的边排到了
    后面,实际上长边根本没有用到,和Prim前面的剔除长边道理相通。
    话说回来,如果道路是单向的,那么Prim就不能剔除边,但这时用
    克鲁斯卡尔也是错的,因为克鲁斯卡尔不会考虑方向,比如存在两条边
    a-->b c-->b 若用克鲁斯卡尔加入并查集,则a,c便在一个并查集里,
    但实际上a,c是不相通的。




  • 相关阅读:
    JS数组的相关方法
    JS字符串的相关方法
    重回我的园区
    STM32F405串口UART4波特率注意问题
    NXP MCU开始学习中
    锂电池测试
    FPGA与STM32并口通信
    NXP LPC4350绝对强悍,准备入手学习
    SDRAM+FPGA+MCU
    STM32+FPGA通信成功
  • 原文地址:https://www.cnblogs.com/caiyishuai/p/13271300.html
Copyright © 2011-2022 走看看