noip模拟赛 保留道路

【问题描述】

很久很久以前有一个国家,这个国家有N个城市,城市由1,2,3,…,N标号,城市间有M条双向道路,每条道路都有两个属性g和s,两个城市间可能有多条道路,并且可能存在将某一城市与其自身连接起来的道路。后来由于战争的原因,国王不得不下令减小花费从而关闭一些道路,但是必须要保证任意两个城市相互可达。

道路花费的计算公式为wG*max{所有剩下道路的属性g}+wS*max{所有剩下道路的属性s},其中wG和wS是给定的值。国王想要在满足连通性的前提下使这个花费最小,现在需要你计算出这个花费。

【输入格式】

输入文件名为road.in。

第一行包含两个正整数N和M。第二行包含两个正整数wG和wS。

后面的M行每行描述一条道路,包含四个正整数u,v,g,s,分别表示道路连接的两个城市以及道路的两个属性。

【输出格式】

输出文件名为road.out。

输出一个整数,表示最小花费。若无论如何不能满足连通性,输出-1。

【输入输出样例】

road.in

3 3 
2 1 
1 2 10 15 
1 2 4 20 
1 3 5 1

road.out

30

【数据规模与约定】

对于10%的数据,N≤10,M≤20;对于30%的数据,N≤100,M≤1000;对于50%的数据,N≤200,M≤5000;

对于100%的数据,N≤400,M≤50000,wG,wS,g,s≤1000000000。

分析:最小生成树问题,只不过有两个量对答案有影响.这种题不可能同时考虑两个量,要消除一个量的影响,就先按照g的大小排序.由于g是排好序的,从小到大枚举边,然后求最小生成树,这就是主要的思路.

      这个算法的瓶颈就在于每次插入一条边都要对所有的边排序做最小生成树,非常耗时间.因为有很多边经常被用到,我们可以在处理的时候把它们维护成有序的,这个时候只需要考虑s的大小就可以了,因为max{g}的大小是已知的.每次不用排序了,但是在这么多点中做克鲁斯卡尔算法还是比较慢.

      如果新加入一条边,那么组成最小生成树的N条边一定是原来最小生成树的N-1条边和这条新加进来的边中取N-1条边.因为没有选到原来的最小生成树的边一定没有最小生成树上的边优,新加进来的这条边由于不知道它s的具体大小,所以可能在最小生成树中,这样维护一个N条边的边集,每次在上面求最小生成树就可以了.

      两个因素互相影响的可以先想办法消除其中一个因素的影响,再来考虑另外一个因素的影响.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const ll inf = 1ll << 60;
ll n, m, wG, wS, fa[510], top, tree[510], ans = inf;

struct node
{
    ll u, v, g, s;
}e[50010];

bool cmp(node a, node b)
{
    return a.g < b.g;
}

ll find(ll x)
{
    if (x == fa[x])
        return x;
    return fa[x] = find(fa[x]);
}

int main()
{
    scanf("%lld%lld", &n, &m);
    scanf("%lld%lld", &wG, &wS);
    for (int i = 1; i <= m; i++)
        scanf("%lld%lld%lld%lld", &e[i].u, &e[i].v, &e[i].g, &e[i].s);
    sort(e + 1, e + 1 + m, cmp);
    for (int i = 1; i <= m; i++)
    {
        bool flag = false;
        for (int j = 1; j <= n; j++)
            fa[j] = j;
        int cur = top + 1;
        for (int j = 1; j <= top; j++)
            if (e[tree[j]].s > e[i].s)
            {
                cur = j;
                break;
            }
        top++;
        if (cur == top)
            tree[cur] = i;
        else
        {
            for (int j = top; j > cur; j--)
                tree[j] = tree[j - 1]; 
            tree[cur] = i;
        }
        int num = 0;
        for (int j = 1; j <= top; j++)
        {
            int fx = find(e[tree[j]].u), fy = find(e[tree[j]].v);
            if (fx != fy)
            {
                fa[fx] = fy;
                tree[++num] = tree[j];
                if (tree[j] == i)
                    flag = 1;
            }
        }
        if (num == n - 1 && flag) //如果第i条边的g为最大的g,那么这条边肯定要被选到
            ans = min(ans, wG*e[i].g + wS*e[tree[num]].s);
        top = num;
    }
    if (ans == inf)
        printf("-1");
    else
        printf("%lld\n", ans);

    return 0;
}

 

posted @ 2017-10-27 21:55  zbtrs  阅读(412)  评论(0编辑  收藏  举报