Lazy Salesgirl-线段树优化

本文讨论了一个关于利用线段树解决售卖面包问题的算法。通过分析顾客购买行为和时间间隔,作者提出了一种有效的方法来确定最大化平均售价的最长时间间隔。通过线段树的数据结构,实现高效的时间间隔枚举和顾客购买行为统计。

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

参考 http://www.cnblogs.com/wuyiqi/archive/2012/04/28/2474672.html的,加上自己的一些理解。
话说上面这个人,我一看到他的线段树就觉得亲切,因为跟我的写法是一样的。
再仔细一看他的博客,发觉我的kmp是跟着他做的。
再看一眼,发觉他跟我的方向一样,都是线段树。真是相见恨晚啊!
这题应该仔细理解排序那里(相当于离散化)和三棵线段树那里。
发觉线段树真神奇。
/*
Pro:
     题目比较有趣,卖面包,如果超过W时间没人来就会睡着,
     再来的人就会把它吵醒,但是不会买,
     继续再等超过W时间没人来就会睡着……已知卖给每个人的面包价格,
     并且卖给第k个人的面包数量(确实卖掉的)为1+(k-1) mod 3,
     也就是1,2,3,1,2,3……这样卖,求最大的W,
     使得卖出面包的平均价格最高。题目假设没有任何两个人来的时间相同,
     并且严格超过W时间,卖主才会睡着(刚好W时没事)。
Sol:
最先的想法:
    将每个人买的面包个数,单价纪录下来,并插入到线段树里面。
    同时将两个人的时间间隔纪录下来。
    枚举时间间隔(从1开始到最大时间间隔),
    对于每个时间间隔,将不满足的人做个标记,统计最后的单价。
    对于所有枚举得到的单价,纪录最大单价和相应的时间间隔。

改进:
    枚举时间间隔的时候不用从1开始。
    如果有个顾客和上一个顾客间的时间间隔超过了w,
    这个顾客就不买东西,而与上一个顾客来的时间间隔小于等于w的顾客
    肯定能买到面包。
    因为答案肯定是已有的时间间隔,所以枚举已有时间间隔就行。
正解:
    
    做法:我觉得关键是要抓住题目的特点,深挖下去,售货员每隔w时间会睡着,那就意味着,

1:如果有个顾客和上一个顾客间的时间间隔超过了w,这个顾客就不糊买东西,而与上一个顾客来的时间间隔小于等于w的顾客肯定能买到面包

所以我们只需枚举每个时间间隔既可,答案肯定就是某个时间间隔,一旦时间间隔定了,那能卖的面包数以及买到东西的顾客数也就定了

2:买到面包的数量问题,由题目给出的式子可得,卖出面包数量的序列 为1 2 3 1 2 3.。。。所以如果知道了某个人买了几个面包,那么可以

推算出接下来第x个人买了几个面包,因为最多也只有三种情况,所以把三种情况的结果都保存一下,就用到了三个线段树,其实就是线段树的域开个二维数组

线段树的域:

sum[rt]:记录当前节点的区间内共有几个顾客

pp[rt][3]:记录当区间最左边的人分别买了 1 2 3个面包时总的销售额

那么我们可以通过线段树将这个结果传递上去

关键代码:

for(int i=0;i<3;i++)

        pp[x][i]=pp[x<<1][i]+pp[x<<1|1][(sum[x<<1]+i)%3];

知道了左子树第一个人买的面包个数,和左子树的人数,自然就可以推出右子树第一个人买的面包个数,三种情况都记录一下,然后再传递上去

最后的结果是pp[1][0],因为第一个人肯定只买了一个面包;

       
    把时间差相同的都插入进去之后,算一个总和的平均值,取最大的即可。
    因为W不断增大的,所以原来卖出去的一直能卖出去,线段树中id不断增多,不用删除。
date:12/08/08
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define lson l , m , rt << 1
#define rson m + 1, r, rt << 1 | 1
#define maxn 100010
using namespace std;
//sum[]表示在这个区间里面来了多少人
int sum[maxn << 2];
//p表示在这个区间里面卖了多少钱
double p[maxn << 2][3];
struct customer{
    int id,t;
    double price;
    bool operator < (const customer &cmp) const{
        return t < cmp.t;
    }
}cusa[maxn],cusb[maxn];
void update(int pos, int l, int r, int rt){
//pos表示是第几个来的。
    sum[rt] ++;
    if(l == r){
        for(int i = 0; i < 3; i ++)
            p[rt][i] = 1.0 * (i + 1) * cusa[pos].price;//
        return;
    }
    int m = (l + r) >> 1;
    if(pos <= m)  update(pos,lson);
    else    update(pos,rson);

    for(int i = 0; i < 3; i ++)
        p[rt][i] = p[rt << 1][i] + p[rt << 1 | 1][(sum[rt << 1] + i)%3];
}
int t,n;
int main(){
    scanf("%d",&t);
    while(t --){
        scanf("%d",&n);
        for(int i = 1; i <= n; i ++){
            scanf("%lf",&cusa[i].price);
        }   for(int i = 1; i <= n; i ++){
            scanf("%d",&cusa[i].t);
        }
//先按来的时间排,并用id记录这位顾客是第几位顾客,用t记录他与前一位顾客的时间间隔
        sort(cusa + 1, cusa + 1 + n);

        cusa[0].t = 0;
        for(int i = 1; i <= n; i ++){
            cusb[i].t = cusa[i].t - cusa[i - 1].t;
            cusb[i].id = i;
        }
        memset(sum,0,sizeof(sum));   memset(p,0,sizeof(p));
        sort(cusb + 1, cusb + 1 + n);
        double ans_aver= 0,ans_w,tmp;

        for(int i = 1,j = 1; i <= n; i = j){
        //枚举时间间隔,相同间隔的一起处理,因为时间间隔相同的才能买到面包
            while(cusb[i].t == cusb[j].t && j <= n){
                update(cusb[j].id,1,n,1);    j ++;
            }
            tmp = p[1][0] / (sum[1] * 1.0);
            if(ans_aver < tmp){
                ans_aver = tmp;
                ans_w = cusb[i].t;
            }
        }
        printf("%.6lf %.6lf\n",ans_w,ans_aver);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值