「USACO08JAN」电话线Telephone Lines 解题报告

本文介绍了一种在加权无向图中寻找特定条件最短路径的算法,通过二分查找优化路径上第K+1大的边权,确保路径费用最小。算法巧妙地结合了图论和搜索技巧,提供了高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

大意:在加权无向图上求出一条从 \(1\) 号结点到 \(N\) 号结点的路径,使路径上第 \(K + 1\) 大的边权尽量小。

思路:

由于我们只能直接求最短路,不能记录过程中的具体的边——那样会特别麻烦

所以,我们就尝试着去想更优的办法

题目中所说,能够免去 \(K\) 条边的费用,那么对于要建设的边中,肯定免去费用最大 $K $ 条边更优

我们关注的应该是第 \(K+1\) 大边的边权,因为其他边权大于这条边边权的边都会被略去

这条边可以枚举吗?

似乎不行,枚举的复杂度还要在最短路的基础上,乘上一个 \(M\) ,不就TLE(Time Limit Enough)了吗……

那么——对于这个边的边权,能不能二分呢?

首先,二分需要满足单调性

题目中,这条第 \(K+1\) 大的边边权越小,能够满足的情况越少,反之亦然

因此我们可以使用二分的方法来查找答案

怎么判断满足或不满足

对于边权大于 \(mid\) 的边,我们改变它的边权为1,否则为0,然后再跑最短路,这样得出来的结果应该是从 \(1\) 号结点到 $N $ 号结点至少要有几条大于 \(mid\) 的边,然后与 \(K\) 比较,小于 \(K\) 则满足,否则不满足

Code:

#include<bits/stdc++.h>
#define INF 0x7f7f7f7f
#define M 10010
#define N 1010
using namespace std;
struct node{
    int to,cost;
    int nxt;
    node(int a,int b):to(a),cost(b){    }
    node(){ }
}b[M<<1];
int head[N],d[N],vis[N];
int n,m,res,t,ans=INF;//初值一样要为INF
int read()
{
    int s=0;
    char c=getchar();
    while(!isdigit(c))
        c=getchar();
    while(isdigit(c))
    {
        s=(s<<1)+(s<<3)+c-'0';
        c=getchar();
    }
    return s;
}
void add(int x,int y,int cost)//建边,正反一起
{
    b[++t]=node(y,cost);
    b[t].nxt=head[x];
    head[x]=t;
    b[++t]=node(x,cost);
    b[t].nxt=head[y];
    head[y]=t;
    return;
}
bool BFS(int k)//好吧,这里不算严格的最短路,因为边权变成了0和1,可以直接宽搜搞定,但是下面的程序,
{//明明是一个真SPFA,假宽搜
    int i,to,cur,cost;
    for(i=1;i<=n;i++)
    {
        d[i]=INF;
        vis[i]=0;
    }
    queue<int>p;
    p.push(1);
    vis[1]=1;
    d[1]=0;
    while(!p.empty())
    {
        cur=p.front();p.pop();
        vis[cur]=0;
        for(i=head[cur];i;i=b[i].nxt)
        {
            to=b[i].to;
            cost=d[cur]+(b[i].cost>k);//处理边权
            if(cost<d[to])
            {
                d[to]=cost;
                if(!vis[to])
                {
                    vis[to]=1;
                    p.push(to);
                }
            }
        }
    }
    if(d[n]<=res)//判断
        return 1;
    return 0;
}
int main()
{
    int i;
    int x,y,cost;
    int l,r,mid;
    n=read();m=read();res=read();
    l=r=0;
    for(i=1;i<=m;i++)
    {
        x=read();y=read();cost=read();
        add(x,y,cost);//建边,注意是双向的
        r=max(r,cost);//r的上限,可以自己赋为1e6,这是题目中给的最大值
    }
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(BFS(mid))//判断
        {
            ans=mid;
            r=mid-1;
        }
        else
            l=mid+1;
    }
    if(ans==INF)//初值应该为INF,不能为0,因为有可能电信公司比较大方,免费之类的——给我多好
        ans=-1;
    printf("%d",ans);
    return 0;
}

另外提供一个比较high的评测

在洛谷过了,这里不一定过哦!

转载于:https://www.cnblogs.com/hovny/p/10237742.html

### USACO P2035 iCow 题目解析 #### 问题描述 Farmer John 购买了一台新的 MP3 播放器 iCow,其中存储了 N (1 ≤ N ≤ 1,000) 首歌曲,每首歌都有一个初始权值 Ri (1 ≤ Ri ≤ 10,000)[^4]。播放顺序由 FJ 设计的独特算法决定: - 下一首播放的是当前所有未播放过的歌曲中权值最高的那一首;若有多个最高权值,则选择编号最小的一首。 - 当某首歌曲播放完成后,其权值会均匀分配给其余 N − 1 首歌曲,并将其自身的权值设为零。 - 如果该歌曲的权值不能被 N − 1 整除,则剩余部分将以 1 单位的形式依次给予排名靠前但尚未获得额外权重的歌曲。 任务是求出按照上述规则最先播放的 T (1 ≤ T ≤ 1000) 首歌曲的具体情况。 #### 解决方案思路 为了模拟这个过程并找到最开始播放的 T 首歌曲,可以采用优先队列(最大堆)来管理待播列表及其对应的权重。每次取出具有最大权重的元素作为即将播放的对象,在更新其他成员的新权重之后重新加入到队列当中继续循环直至达到所需次数为止。 具体步骤如下: - 初始化数据结构:创建一个包含所有歌曲 ID 和它们各自起始分数的最大堆; - 输出此目标的信息; - 更新剩余项目的得分并将已处理项移回至集合内等待下次轮转; 下面给出完整的 C++ 实现代码示例: ```cpp #include <iostream> #include <queue> using namespace std; struct Song { int id; long score; }; bool operator<(const Song& a, const Song& b){ return !(a.score > b.score || (a.score == b.score && a.id < b.id)); } int main(){ priority_queue<Song> pq; int n,t,r; cin>>n>>t; for(int i=1;i<=n;++i){ cin >> r; pq.push({i,r}); } while(t--){ auto top_song=pq.top(); cout<<top_song.id<<"\n"; vector<int> remainders; pq.pop(); if(top_song.score%(n-1)!=0){ for(int j=0;j<top_song.score%(n-1);++j) remainders.push_back(j); } while(!pq.empty()){ Song current = pq.top(); pq.pop(); current.score += top_song.score/(n-1); if (!remainders.empty()) { current.score++; remainders.erase(remainders.begin()); } pq.push(current); } // Reinsert the played song with zero points back into queue. pq.push({top_song.id, 0}); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值