Cpp环境【POJ3622】【Usaco2008 Jan Silver】【Vijos1874】Telephone Lines架设电话线

Description  【问题描述】

Farmer John wants to set up a telephone line at his farm. Unfortunately, the phone company is uncooperative, so he needs to pay for some of the cables required to connect his farm to the phone system.

There are N (1 ≤ N ≤ 1,000) forlorn telephone poles conveniently numbered 1..N that are scattered around Farmer John’s property; no cables connect any them. A total of P (1 ≤ P ≤ 10,000) pairs of poles can be connected by a cable; the rest are too far apart.

The i-th cable can connect the two distinct poles Ai and Bi, with length Li (1 ≤ Li ≤ 1,000,000) units if used. The input data set never names any {Ai, Bi} pair more than once. Pole 1 is already connected to the phone system, and pole N is at the farm. Poles 1 and N need to be connected by a path of cables; the rest of the poles might be used or might not be used.

As it turns out, the phone company is willing to provide Farmer John with K (0 ≤ K < N) lengths of cable for free. Beyond that he will have to pay a price equal to the length of the longest remaining cable he requires (each pair of poles is connected with a separate cable), or 0 if he does not need any additional cables.

Determine the minimum amount that Farmer John must pay.


  FJ打算将电话线引到自己的农场,但电信公司并不打算为他提供免费服务。于是,FJ必须为此向电信公司支付一定的费用。
  FJ的农场周围分布着N(1 <= N <= 1,000)根按1..N顺次编号的废弃的电话线杆,任意两根电话线杆间都没有电话线相连。一共P(1 <= P <= 10,000)对电话线杆间可以拉电话线,其余的那些由于隔得太远而无法被连接。第i对电话线杆的两个端点分别为A_i、B_i,它们间的距离为L_i (1 <= L_i <= 1,000,000)。数据中保证每对{A_i,B_i}最多只出现1次。编号为1的电话线杆已经接入了全国的电话网络,整个农场的电话线全都连到了编号为N的电话线杆上。也就是说,FJ的任务仅仅是找一条将1号和N号电话线杆连起来的路径,其余的电话线杆并不一定要连入电话网络。
  经过谈判,电信公司最终同意免费为FJ连结K(0 <= K < N)对由FJ指定的电话线杆。对于此外的那些电话线,FJ需要为它们付的费用,等于其中最长的电话线的长度(每根电话线仅连结一对电话线杆)。如果需要连结的电话线杆不超过K对,那么FJ的总支出为0。
  请你计算一下,FJ最少需要在电话线上花多少钱。

Input  【输入格式】
  • Line 1: Three space-separated integers: N, P, and K
  • Lines 2..P+1: Line i+1 contains the three space-separated integers: Ai, Bi, and Li

  第1行: 3个用空格隔开的整数:N,P,以及K
  第2..P+1行: 第i+1行为3个用空格隔开的整数:A_i,B_i,L_i

Output  【输出样例】

* Line 1: A single integer, the minimum amount Farmer John can pay. If it is impossible to connect the farm to the phone company, print -1.


  输出一行一个整数,为FJ在这项工程上的最小支出。如果任务不可能完成,输出-1。

Sample Input  【输入样例】

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

Sample Output  【输出样例】

4

【样例解释】

  一共有5根废弃的电话线杆。电话线杆1不能直接与电话线杆4、5相连。电话线杆5不能直接与电话线杆1、3相连。其余所有电话线杆间均可拉电话线。电信公司可以免费为FJ连结一对电话线杆。
  FJ选择如下的连结方案:1->3;3->2;2->5,这3对电话线杆间需要的电话线的长度分别为4、3、9。FJ让电信公司提供那条长度为9的电话线,于是,他所需要购买的电话线的最大长度为4。

Source  【原题传送门】

POJ3622 原题传送门

【思路梳理】

  题偏难,很好综合了数据结构以及二分猜答案的思想。考虑到每一个电线杆可以视作一个点,每一条电话线可以视为一条无向带权边,这样转化为一道图论题,使用Dijistra/SPFA;涉及到求最大值最小问题,显然应该考虑的是二分猜答案思想。
  那么大致思路就出来了:既然有k条边我们可以无代价地让电信局修建,那么这k条边就应该是权值最大的前k条边,把剩下的边交给FJ自己处理。那么显然,将最优路径构建后,我们的答案就是第k+1长的边的长度。二分猜答案,猜测的是第k+1条边的长度(不妨记为x):如果第i条边的长度超过了x,那么这条边按照我们的思路是应该交给电信局来修建的;否则的话交给FJ处理,不处理这一条边 (原因很简单:我们使用的是二分猜答案,猜测第k+1长的边的长度,这一条边不一定就是我们要求的第k+1长的那条边,即使就算是这样,记录这条边也无意义,因为我们已经在二分猜答案且这不会、一定会是最后的结果)。
  所以不妨将长度大于x的边的权值记为1,等于小于x的边记为0,看从起点1到终点n的距离dist[n]是否大于k,如果大于k,则说明需要电信局修建多于k条边(这k条边的长度都大于我们猜测的值x),猜测值不成立,需要在原区间[s,d]中更大的半个区间[x+1,d];如果猜测值成立,检查是否能够让边的总长度更小(即让电信局修的边的数量更多),我们应该在更小的半个区间[s,x-1]猜测。
  考虑什么时候输出-1?从1不能达到n点的时候。那么我们的dist[n]就会一直等于inf,即无论让电信局修的边长度多大、修的边的数量多少都不能够使得我们的结点n符合要求。

  下面给出代码,亲测能通过所有数据,10组数据108ms。

【Cpp代码】
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
#include<algorithm>
#define maxn 1005
#define maxp 10005
#define inf 0x3fffffff //无穷大的另一种写法,大家可以学来提升*哔*(屏蔽音)格 
using namespace std;
int n,p,k,dist[maxn],qn[maxn];
vector<int>g[maxn],w[maxn];//图的存储结构不需要赘述
bool inq[maxn];

void SPFA(int t)//SPFA/Dijstra都可
{
    for(int i=1;i<=n;i++)   dist[i]=inf;//dist[i]的含义:在当前猜测值下从起点1连接到结点i所需要的由电信局修建的边的数量 
    memset(inq,false,sizeof(inq));
    memset(qn,0,sizeof(qn));
    queue<int>q;
    q.push(1);
    inq[1]=true;
    dist[1]=0;
    while(!q.empty())
    {
        int i=q.front();q.pop();inq[i]=false;
        for(int j=0;j<g[i].size();j++)
        {
            int k=g[i][j],c=w[i][j]>t ? 1:0 ;
            //如果这条边的长度大于了我们的猜测值,那么就应该交给电信局 
            if(dist[k]>dist[i]+c)
            {
                dist[k]=dist[i]+c;
                if(!inq[k])
                {
                    inq[k]=true;
                    qn[k]++;
                    if(qn[k]>n)     return ;//负权回路应该是不存在吧? 
                    q.push(k);
                }
            }
        }
    }
}

bool check(long long x)//二分猜答案,保险起见还是用了long long(因为d是inf,如果s也很大的话两者一加会超过int) 
{
    SPFA(x);
    if(dist[n]<=k)  return true;//注意一点,可以在这里判断是否能够从结点1找到一条路径到达结点n 
    return false;//如果不存在这样的一条路径,那么dist[n]显然等于inf 
}

void solve()
{
    int s=0,d=inf,ans=-1;//ans初值等于-1,即在目前猜测的int范围内无论猜测值多大都不能够使得dist[n]小于k,那么显然dist[n]=inf 
    while(s<=d)
    {
        long long mid=(long long)s+d>>1;//除以2的另一种写法,位运算 
        if(check(mid))  ans=mid,d=mid-1;//mid成立,看mid能否更小让电信局修更多边 
        else s=mid+1;
    }
    printf("%d",ans);
}

int main()
{
    freopen("in.txt","r",stdin);
    scanf("%d%d%d",&n,&p,&k);
    for(int i=1;i<=p;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        g[x].push_back(y);w[x].push_back(z);
        g[y].push_back(x);w[y].push_back(z);//建立存储结构 
    }
    solve();


    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值