【JZOJ】3187 的士

Description

在高速公路上(0<=M<=1e9),路旁有N(N <= 100000)头奶牛,他们的出发地和目的地分别是Xi,Yi。
你从高速公路的起点0出发,每次只能载一头奶牛,要把所有奶牛送到目的地,并且最终要到高速公路的尽头M,求最短的路程和。
PS.为了最短,我们可以在中途抛下某头奶牛,只要最后每头奶牛都到目的地就行。

Analysis

这题有两个要求:
1、所有奶牛到目的地。
2、最终要到尽头。
注意 Xi Yi 是无序的,既可以有 (1,2) ,也可以有 (7,2)

首先这个 M 太大,我们先离散化。
得到2N个位置,此时位置与位置之间的距离就不是1了而是离散化的距离了。
如果 Xi<Yi ,那我们不难想到这样的流程:
从0开始走,每一步都把当前所有奶牛带上(由于一次只能带1个所以往返多次带走),带到下一个位置。
如图:
这里写图片描述
我们可以先开车开到a接上牛A。
然后载着牛A去b接牛B。
由于只能载一头牛,所以我们要在b–>c之间走3趟,去一次,回来,再去一次。
最后载着牛b去d点,牛A已经在c到站了。

这一定是最优的,因为我们已经尽可能地少走每一段路了,要走的都是必须走的。

但是题目可能存在 Xi>Yi

对于 Xi>Yi 我们可以理解为搭了顺风车,【我们在 Xi<Yi 的时候返程是没有载人的】,如果能搭顺风车那么这对 XiYi 就是免费的。
到这里就是我比赛时候的思路了。
我打了一个堆来维护 Xi<Yi 有至少要多少路程,同时统计顺风车的数量。 Xi>Yi 能搭顺风车就搭不能就多跑一段路。

其实这个已经无限接近正解了。
但有些特殊情况没有处理到。这里写图片描述
对于绿色的牛,是可以搭顺风车回去的。
但是对于粉色的牛,没有顺风车可以搭,只能单独接他到目的地。
由于我们讨论的顺风车搭不上于是就多跑一段路,导致d到e没有被算上。然后就只有60分了。
事后我也加了点特判但是无济于事。我们需要换下想法。

其实对于回来的点也是一样的嘛,如果我们把出发点设为M的话。
我们用 Prei 记录 i 位置被多少对Xi<Yi跨越
Sufi 记录 i 位置被多少对Xi>Yi跨越。
那么对于 i 位置到i+1位置的路径应该走多少遍呢?
i 这个位置有Prei头牛要到 i+1
i+1 这个位置有 Sufi+1 头牛要到 i
如果Prei>Sufi+1,那么要跑这段路 Prei21 趟。
如果 Prei<=Sufi+1 ,那么要跑这段路 Sufi+12+1 趟。
对于0的特殊情况讨论一下【其实这样已经不用特殊讨论了,但是最好讨论一下加深一下理解】
Prei,Sufi ,我们可以通过打标记的前缀和、树状数组、堆等各种算法弄出来。
最后别忘了我们从0出发到M去,把这两个位置加入离散化然后一起讨论就好了。

不懂的看看代码吧。

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
using namespace std;

typedef long long LL;
const int N = 101000;

priority_queue<int , vector<int> , greater<int> > Q;
priority_queue<int> Q2;

map<int , int> Hash;

struct Data{
    int x,y;
}a[N],b[N];

int c[N * 2],id,tot,tot2,td,n,m,mxt;
LL P[N * 2],ans,sum[N * 2],sum2[N * 2];

bool cmp(Data x , Data y) { return x.x < y.x;}
bool cmp2(Data x , Data y) { return x.x > y.x;}

void Relable()
{
    sort(c , c + 1 + td);
    for (int i = 0 ; i <= td ; i ++)
    {
        int p = Hash[c[i]];
        if (!p) Hash[c[i]] = ++ id , P[id] = c[i]; else continue;
    }
    for (int i = 1 ; i <= tot ; i ++) a[i].x = Hash[a[i].x] , a[i].y = Hash[a[i].y];
    for (int i = 1 ; i <= tot2 ; i ++) b[i].x = Hash[b[i].x] , b[i].y = Hash[b[i].y];
}

void Solve()
{
    int pos = a[1].x , l = 1;
    while (a[l].x == pos && l <= tot) Q.push(a[l].y) , ++ l;
    while (pos <= id)
    {
        sum[pos] = Q.size();
        pos ++;
        while (a[l].x == pos && l <= tot) Q.push(a[l ++].y);
        int p = Q.top();
        while (Q.top() <= pos && !Q.empty()) Q.pop();
    }
    pos = b[1].x , l = 1;
    while (b[l].x == pos && l <= tot2) Q2.push(b[l ++].y);
    while (pos)
    {
        sum2[pos] = Q2.size();
        pos --;
        while (b[l].x == pos && l <= tot2) Q2.push(b[l ++].y);
        while (Q2.top() >= pos && !Q2.empty()) Q2.pop();
    } //此处可以好好恶搞= = 我打了堆比较丑 
    //sum就是Pre,sum2就是Suf 

    //此处可以不用讨论= = 
    for (int i = 1 ; i < id ; i ++) 
        if (sum[i] && sum2[i + 1])
        {
            if (sum[i] > sum2[i + 1]) ans += (sum[i] * 2 - 1) * (P[i + 1] - P[i]);
                else ans += (sum2[i + 1] * 2 + 1) * (P[i + 1] - P[i]);
        } else {
            if (!sum2[i + 1] && sum[i]) ans += (sum[i] * 2 - 1) * (P[i + 1] - P[i]);
            if (sum2[i + 1] && !sum[i]) ans += (sum2[i + 1] * 2 + 1) * (P[i + 1] - P[i]);
            if (!sum2[i + 1] && !sum[i]) ans += (P[i + 1] - P[i]);
        }
}

int main()
{
    freopen("1.in" , "r" , stdin);freopen("1.out" , "w" , stdout);

    scanf("%d%d" , &n , &m);
    c[0] = 0;
    for (int i = 1 ; i <= n ; i ++)
    {
        int x,y;scanf("%d%d" , &x , &y);
        if (x < y && x != y) a[++ tot].x = x , a[tot].y = y , mxt = max(mxt , y);
            else if (x != y) b[++ tot2].x = x , b[tot2].y = y , mxt = max(mxt , x);
        if (x != y) c[++ td] = x , c[++ td] = y;
    }
    c[++ td] = m;
    Relable();//离散化 
    sort(a + 1 , a + 1 + tot , cmp);sort(b + 1 , b + 1 + tot2 , cmp2);
    Solve();
    printf("%I64d\n" , ans);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值