奶牛跑步(Treap)

题目描述

有N只奶牛在比赛跑步,他们围绕一个长C的圆形跑道跑L圈。所有奶牛在同一个地方起跑,但是不同奶牛的速度不一样。而比赛在最快的奶牛跑完L圈后结束。
现在FJ想知道有多少次“超车”。一次“超车”定义为:在比赛开始后,到比赛结束为止(包含比赛结束的时刻),一只奶牛x赶超了另一只奶牛y,即x,y出现在同一个位置。

输入格式 

第一行有三个整数:N,L,C,
第2到第N+1行:第i+1行有一个整数,代表奶牛i的速度speed_i

输出格式 

输出只有一行,包含一个整数,即“超车”的总次数

输入样例 

4 2 100
20
100
70
1

输出样例 

4

【样例解释】
比赛持续了2个单位时间。期间有4次超车:奶牛2超过了奶牛1和奶牛4,奶牛3超过了奶牛1和奶牛4。

【数据范围】
对于60%的数据,1≤N≤5000
对于100%的数据,1≤N≤100000, 1≤L,C≤25000, 1≤speed_i≤1000000

题解:

       60%的数据是n^2的时间复杂度,如下:

       首先我们知道:

       1.只可能是速度快的追上速度慢的,我们先把奶牛按速度从大到小排序。

       2.假如奶牛i跑了a圈,奶牛j跑了b圈,(a>b),那么奶牛i可追上奶牛j(a-b)次

       等等,a和b并不一定是整数,如果是整数,我们把奶牛跑的圈数从大到小插入队列,维护一个圈数的前缀和,就可以O(1)求出一个奶牛被追上的圈数。比如我们把前i个奶牛都插入了队列,得到前缀和sum【i】,现在第i+1只奶牛跑了c圈,那么被追上的次数为sum【i】-i*c。

       事实总是与愿望相违,假如a-b是实数,则追上的次数要向下取整,那么我们用前缀和维护就会错。所以我们只能用(n^2)的方法去求了。

       100%的正解,也运用了前缀和的思想,对于是否要向下取整的问题,我们用treap来解决。假如a的小数部分>=b的小数部分,那么向下取整与不向下取整的结果是一样的,反之则向下取整会比原来少了1。向下取整的影响只来自小数部分,所以对于整数部分我们仍用前缀和维护,小数部分我们开一个treap,每次可O(log n)时间查找小数部分比它大(或少)的个数有多少个,再来更新一下答案,最后把当前的奶牛信息存入treap。此题就此解决。

#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;

long long cir[100050];
int n,c,cnt,ge;
struct Treap
{
    Treap *l,*r;
    int fix,siz;
    long long x;
}tree[200050];
Treap *root;
long long ans,nows,nowi,l,spe[100050];

bool cmp(int a,int b)
{
    return a>b;
}

int Qs(Treap *a)
{
    if (a==NULL) return 0;
    return a->siz;
}

void Lefturn(Treap *&a)
{
    Treap *b = a->r;
    a->r = b->l;
    b->l = a;
    a->siz = Qs(a->l)+Qs(a->r)+1;
    b->siz = Qs(b->l)+Qs(b->r)+1;
    a = b;
}

void Righturn(Treap *&a)
{
    Treap *b= a->l;
    a->l = b->r;
    b->r = a;
    a->siz = Qs(a->l)+Qs(a->r)+1;
    b->siz = Qs(b->l)+Qs(b->r)+1;
    a = b;
}

Treap *NewNode(long long x)
{
    cnt++;
    tree[cnt].x = x;
    tree[cnt].fix = rand();
    tree[cnt].siz = 1;
    tree[cnt].l = NULL;
    tree[cnt].r = NULL;
    return tree+cnt;
}

void Updata(Treap *&a,long long x)
{
    if (!a)
    {
        a = NewNode(x);
        return;
    }
    else if (a->x>=x)
    {
        Updata(a->l,x);
        a->siz = Qs(a->l)+Qs(a->r)+1;
        if (a->l->fix<a->fix) Righturn(a);
    }
    else
    {
        ge = ge+Qs(a->l)+1;
        Updata(a->r,x);
        a->siz = Qs(a->l)+Qs(a->r)+1;
        if (a->r->fix<a->fix) Lefturn(a);
    }
}

int main()
{
    freopen("2242.in","r",stdin);
    freopen("2242.out","w",stdout);
    srand(937);
    scanf("%d%d%d",&n,&l,&c);
    for (int i=1; i<=n; i++)
    scanf("%lld",&spe[i]);
    sort(spe+1,spe+1+n,cmp);
    cir[1] = l*1000000;
    for (int i=2; i<=n; i++)
    cir[i] = (long long)(l*spe[i]*1000000)/spe[1];
    
    Updata(root,((long long)cir[1]%1000000));
    ans = 0;  nows = ((long long)(cir[1])/1000000); 
    for (int i=2; i<=n; i++)
    {
        ge = 0;
        Updata(root,((long long)cir[i]%1000000));
        nowi = ((long long)(cir[i])/1000000);
        ans = ans+(long long)(nows-nowi*(i-1)-ge);
        nows = nows+nowi;
    }
    
    printf("%lld\n",ans);
    return 0;
}

 

转载于:https://www.cnblogs.com/Janous/p/7568094.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值