百度之星2010 复赛2 购物搜索调研

原题:

问题描述 

       “有啊”是百度旗下的电子商务购物平台,每天都有上百万的用户在平台上搜索自己想要购买的商品。为了给用户更 好的搜索体验,工程师们准备做一次搜索策略的调研工作。

       假设对于某一个特定的查询词来说,一共有N个相关商品,并且通过人工标注得到每个商品与查询词的相关度reli。 当用户输入这个查询词时,可以采取一种非常简单的响应策略,就是在N个相关商品中随机的找一段连续的商品子序列返回给用户。 工程师认为,在每次返回结果中相关度的最小值代表了本次搜索结果的分数score。

       用户在搜索时,心中往往会对结果有个期望分数expect,当搜索结果的分数score高于用户的预期 expect时,该用户的满意度为score – expect;否则用户的满意度为0。

       假设在上述响应策略中,返回的子序列是完全随机的(即每段连续子序列被选中的概率完全相同),你的任务是计算 每个用户满意度的数学期望。

输入格式 

       输入第一行包含一个正整数T,表示测试数据的组数。每组测试数据的第一行为相关商品的数量N, 第二行为N个整数,分别表示每个商品与查询词的相关度reli(用空格分隔)。 第三行为一个正整数M,表示参与调研的用户数量,接下来的M行中的每一行都是一个整数expect,表示该用户在搜索时预期的分数值。 其中1 <= T <= 10,1 <= N, M <= 100,000, -109 <= reli, expect <= 109。

输出格式 

       对于每组测试数据,首先输出一行 “Case #:”,其中#表示测试数据编号(从1开始),注意空格与大小写。 接下来的M行,按照输入顺序依次给出每个用户在这种响应策略下满意度的期望值,每个用户占一行。 为了避免精度问题,期望值用最简分数 “A/B” 表示,其中A与B互质,B是正整数。当结果是整数时,应只输出A。

样例输入 

       3

       3

       13 5

       1

       2

       4

       5-2 1 4

       2

       1

       -1

       3

       24 10

       1

       0

       样例输出

        Case1:

        5/6

       Case2:

        7/10

        3/2

       Case3:

        4

样例说明 

在Case 1中,可能返回的子序列一共有6个[1], [3], [5], [1 3], [3 5], [1 3 5],对应的分数(相关度最小值)为1, 3, 5, 1, 3, 1,用户满意度依次为0, 1, 3, 0, 1, 0。每个子序列被返回的概率相同,即为1/6。根据数学期望的定义 可以得出该用户的满意度期望值是5/6。


题意分析:

题意比较明确,给一个长度为n的序列ai,然后M次询问。每次询问是给出一个数k,然后在序列A里面随机取一段连续的序列,其中这个序列中的最小值为x,然后这个序列的得分是max(x-k, 0),问得分的数学期望是多少。


关键思路:

总共可能产生的序列是n*(n+1)/2,这个是要求的数学期望的分母,关键是分子怎么求。

我们计算将每一个ai作为最小值的序列个数。首先可以通过单调栈处理出ai左边第一个比ai小的位置li,右边第一个比ai小的位置ri,那么在任何序列左端点在[li+1,i]之间,右端点在[i,ri-1]之间的最小值都是ai,个数是(i-li)*(ri-i)。

     但有一种特殊情况,就是[li+1,ri-1]这端区间内不止一个数为ai,也就是最小值不唯一。比如3 2 4 2,在处理第一个2的时候会得到区间{2}.{3,2},{2,4},{3,2,4},{2,4,2},{3,2,4,2},在处理第二个2的时候{3,2,4,2},{2,4,2}这两个区间重复计算了。也就是说同一段区间里面有几个不同的最小值需要去重。

     解决方法也很简单:假设当前处理区间为[l,r],最小值为ai,位置分别为l < p1 <= p2 <= ... <= pm < r,处理p1的时候依然是(p1 - l)*(r - p1),处理p2的时候发现多出的部分为左端点在[p1+1,p2],右端点在[p2,r-1]的部分,因此去重后应当加(p2 - p1) * (r - p2),后面都是同理了。这里需要处理一下每个数前一个和他相同的数的位置。

    最后算答案的时候就是sigma((ai - k)*gi)/(n*(n+1)/2) (ai >= k, gi 为最小值为ai的序列个数),由于询问次数很多,考虑每次取的都是较大的几个ai,排序后处理一下ai的后缀和以及ai*gi后缀和,然后根据查询的k二分位置给出答案就完成了。

测试数据:

4

3

1 3 5

1

2

4

5 -2 1 4

2

1

-1

3

2 4 10

1

0

3

2 2 2

1

1



代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<set>
#include<stack>
#include<map>
using namespace std;

typedef long long LL;
#define CLR(a,b) memset(a,b,sizeof(a))
const int N = 100000+20;

LL gcd(LL a,LL b)
{
    if(a == 0)return b;
    return gcd(b%a,a);
}
map<int,LL> G;
typedef pair<int,int> PII;
int n,m;
int a[N];
LL l[N],r[N];
int bef[N];
PII o[N];
LL sufix[N],sufixsum[N];
int tot;
map<int,LL>::iterator ite;
void solve(LL x)
{
    int pos = upper_bound(o + 1, o + tot + 1, (PII)make_pair(x, 0)) - o;
    LL p = sufixsum[pos] - sufix[pos] * x;
    LL q = n * (n+1) / 2;
    LL d = gcd(p,q);
    p /= d; q /= d;
    if(q == 1) printf("%lld\n",p);
    else printf("%lld/%lld\n",p,q);
}
void init()
{
    CLR(bef,-1);
    sort(o + 1, o + n + 1);
    tot = 0;
    for(int i = 1 ; i <= n ; i ++){
        if(i > 1  && o[i].first == o[i-1].first){
            bef[o[i].second] = o[i-1].second;
        }else o[++tot] = o[i];
    }
    stack<int> st;
    for(int i = 1; i <= n ; i++){
        while(!st.empty() && a[st.top()] >= a[i])st.pop();
        if(st.empty())l[i] = 0;
        else l[i] = st.top();
        st.push(i);
    }
    while(!st.empty())st.pop();
    for(int i = n; i >= 1 ; i--){
        while(!st.empty() && a[st.top()] >= a[i])st.pop();
        if(st.empty())r[i] = n+1;
        else r[i] = st.top();
        st.push(i);
    }
    while(!st.empty())st.pop();
    G.clear();
    for(int i = 1; i <= n ; i ++){
        if(bef[i] == -1){
            G[a[i]] += (i - l[i]) * (r[i] - i);
        }else{
            if(r[bef[i]] < i){
                G[a[i]] += (i - l[i]) * (r[i] - i);
            }else{
                G[a[i]] += (i - bef[i]) * (r[i] - i);
            }
        }
    }
    sufixsum[tot+1] = sufix[tot+1] = 0;
    for(int i = tot ; i >= 1 ; i--){
        int val = o[i].first;
        sufix[i] = sufix[i+1] + G[val];
        sufixsum[i] = sufixsum[i+1] + val * G[val];
    }
}
int main()
{
    int T,cas = 0;
    scanf("%d",&T);
    while(T--){
        cas ++;
        scanf("%d",&n);
        for(int i = 1; i <= n ;i ++){
            scanf("%d",&a[i]);
            o[i] = make_pair(a[i], i);
        }
        init();
        int m;
        scanf("%d",&m);
        printf("Case %d:\n",cas);
        for(int i = 0; i < m ; i ++){
            int x;
            scanf("%d",&x);
            solve(x);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值