USACO Cow Toll Paths, 2009 Dec

过路费
〖题目描述〗
约翰家有N 片草地,编号为1 到N,彼此之间由M 条双向道路连接,第i 条
道路连接草地Ai 和Bj,两片之间可能有多条道路,但没有道路会连接同一片草
地,现有的道路可以保证任意两片草地之间都是连通的。
约翰宣布,从即日开始,为了“低碳节能”,每条道路都要收到过路费,第
i 路收费Li 元。此外,约翰还要求每头奶牛购买牌照,他为每片草地设置了牌
照标准,如果奶牛购买的牌照价格低于某片草地的标准,她将被禁止进入那片草
地。第i 片草地的牌照标准为Ci。
奶牛们敢怒不敢言,有K 头奶牛跑来向你咨询省钱的办法,第i 头奶牛要从
草地Si 走到Ti。请你帮她们算算,选择什么样的路线才能最省钱?
〖输入〗
第一行:三个整数N,M 和K,1≤N≤250,1≤M≤10000,1≤K≤10000
第二行到N+1 行:第i+1 行有一个整数:Ci,1≤Ci≤106
第N+2 行到N+M+1 行:第i+N+1 行有三个整数Ai,Bi 和Li,1≤Ai≤N,1≤Bi≤
N,1≤Li≤106
第N+M+2 行到N+M+K+1 行:第i+N+M+1 行有两个整数Si 和Ti,1≤Si≤N,1≤
Ti≤N
〖输出〗
第一行到K 行:第i 行表示Si 到Ti 的最低费用是多少
〖样例输入〗
5 7 2
2
5
3
3
4
1 2 3
1 3 2
2 5 3
5 3 1
5 4 1
2 4 3
3 4 4
1 4
2 3
〖样例输出〗
8
9
〖样例说明〗
从1 到4 的最好办法是1→3→5→4,从2 到3 的最好办法是2→5→3

方法一:暴力O(n^4 ~ n^2*m)
预计得分40’~60’

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#define N 300
#define M 20010
using namespace std;
const int inf=1<<30;
int n,m,k,c[N],c1[N],pmt[N],tot=0,vis[N],dist[N];
struct Edge{
    int v,l;
};
vector <Edge>e[N];
int ans[N][N];
void init(){
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    for (int i=1;i<=n;i++) c1[i]=c[i];
    sort(c1+1,c1+n+1);
    pmt[++tot]=c1[1];
    for (int i=2;i<=n;i++)
        if (c1[i]!=c1[i-1]) pmt[++tot]=c1[i];
    int a,b,w;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&w);
        e[a].push_back((Edge){b,w});
        e[b].push_back((Edge){a,w});
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) ans[i][j]=inf;
}
void spfa(int s,int w){//认为w为牌照,做最短路径
    memset(vis,0,sizeof vis);
    for (int i=1;i<=n;i++) dist[i]=inf;
    vis[s]=1;dist[s]=0;
    queue <int> q;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();q.pop();
        for (int i=0;i<e[u].size();i++)
        {
            int v=e[u][i].v;
            if (c[v]>w) continue;//进行限制
            if (dist[u]+e[u][i].l < dist[v])
            {
                dist[v] = dist[u]+e[u][i].l;
                if (!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
            vis[u]=0;
        }
    }
    for (int i=1;i<=n;i++)
        if (c[i]<=w) ans[s][i]=min(ans[s][i],dist[i]+w);
}
int main(){
    freopen("toll.in","r",stdin);
    freopen("toll.out","w",stdout);
    init();
    for (int s=1;s<=n;s++)
        for (int i=1;i<=tot;i++)
    if (pmt[i]>=c[s]) spfa(s,pmt[i]);//出现过的牌照费
    int s,t;

    while(k--)
    {
        scanf("%d%d",&s,&t);
        printf("%d\n",ans[s][t]);
    }

    return 0;
}

然而暴力不是全部,要动脑。
进行排序,我们可以得到:1、路径长度是严格不降的;
2、当前i->j的费用=路程+路程上的最大点权。
由于我们是对升序枚举中间点,可以保证最短路持续更新总费用。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#define N 100
using namespace std;
const int inf=1<<30;
struct data{
    int w,id;
}c[N];
int n,m,T,d[N][N],w[N][N],ini[N];
int ans=0;
bool cmp(data a,data b){return a.w<b.w;}
void init(){
    scanf("%d %d %d\n",&n,&m,&T);
    for (int i=1;i<=n;i++) scanf("%d",&c[i].w),c[i].id=i;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) d[i][j]=w[i][j]=inf;
    int a,b,v;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&v);
        d[a][b]=d[b][a]=min(d[a][b],v);
        d[i][i]=0;
        w[i][i]=c[i].w;
    }
    sort(c+1,c+n+1,cmp);
    for (int i=1;i<=n;i++) ini[c[i].id]=c[i].w;//还原排序前的c数组
}
void floyd(){
    for (int id=1;id<=n;id++)
    {
        int k=c[id].id;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
        {
            d[i][j]=d[j][i]=min(d[i][j],d[i][k]+d[k][j]);
            w[i][j]=w[j][i]=min(w[i][j],max(c[id].w,max(ini[i],ini[j]))+d[i][j]);
        }
    }

}
int main(){
    freopen("toll.in","r",stdin);
    freopen("toll.out","w",stdout);
    init();
    floyd();
    int s,t;
    while(T--)
    {
        scanf("%d%d",&s,&t);
        printf("%d\n",w[s][t]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值