2018.8.7T1(贪心)

描述
YJC最近在研究世界杯,他拿一半财产压了德国队,另一半财产压了阿根廷队,结果可想而知。YJC表示非常angry,于是又开始研究博彩公司的盈利原理。

假设在世界杯决赛前,有n个人参与了赌博。第i个人认为法国队赢的概率是p[i]p[i],克罗地亚队赢的概率是1−p[i]。对于每一只球队,如果根据博彩公司给出的赔率第i个人的期望收益非负,则他会给这只球队下注a[i]元(设赔率为x,某人下注了y元,如果他赢了可以返还x∗y元,他输了则会返还0元。不论输赢,下注的钱均不返还)。如果两只球队期望收益均非负,则他会给两只球队各下注a[i]元。

现在博彩公司要决定法国队和克罗地亚队的赔率,使得在自身收益最小的胜负情况下的收益最大。

输入格式
第一行输入一个数n,表示总人数。

接下来n行,每行两个实数a[i]和p[i],均保留不超过6位小数。

输出格式
一个实数,表示无论比赛结果如何,博彩公司最小收益的最大值,保留66位小数。

样例1
样例输入
3
1 0.1
2.233 0.1
3.666 0.1
样例输出
0.000000


题面还是很鬼畜的
我们可以发现当赔率大于 1p[i] 1 p [ i ] 时,会下注
我们同时可以发现,两个队伍的投注是独立的
分开处理。(当然也可以在一起排序分前缀后缀做)
分别按照 1p[i] 1 p [ i ] 排序
我们用 sum[i] s u m [ i ] 表示赔率满足前 i i 个人时前i个人的下注之和
我们用 lose[i] l o s e [ i ] 表示球赛输时前 i i 个人下注亏的钱
考虑一个n2的暴力
枚举两场比赛的赔率(我们显然可以发现当一个赔率在 [1p[i],1p[i+1]) [ 1 p [ i ] , 1 p [ i + 1 ] ) 之间时,我门肯定选 1p[i] 1 p [ i ] 最优)
这样的话我们只要枚举两场比赛的赔率
对所有的 min(suma[i]loseb[j],sumb[j]losea[i]) m i n ( s u m a [ i ] − l o s e b [ j ] , s u m b [ j ] − l o s e a [ i ] ) 取个 max m a x 即可
考虑优化,考虑,当 i i ,确定时,我们对于每个min,我们要尽可能的缩小两边的差距,因为 lose[i],sum[i] l o s e [ i ] , s u m [ i ] 都是递增的,所以肯定是我们让小的增加,让大的减少。
这样的话我们就可以取出 j j (也就是b)中可能的对局,然后我们二分查找这个过程就行了。

实际上理论时间复杂度已经到最优了,应为排序就需要 nlogn n l o g n 的时间,但是后面这个过程实际上可以做到 O(n) O ( n ) ,我们可以发现,实际上两个数组都是满足这个单调性的。也就是说如果 i i 移动了一位,j移动的方向是固定的!
感性的想
i i 越大,下注i的人越多,输的时候亏的越多,那我们就需要更多的人投注 j j 来弥补这个亏损。同时也不能太多,不能让b输的时候亏损过大。
相当于维护一个平衡,也就是两边同时滑

二分代码如下:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define ll long long
int n , nn;
const int INF = 0x7fffffff;
double Max_a,Max_b;
double sum_a[1001000] , sum_b[1001000];
double lose_a[1001000] , lose_b[1001000];
struct point{
    double x , y;
}tmp[1001000];
struct node{
    double p , num;
    int id;
}a[1001000],b[1001000];
int read()
{
    int sum = 0;char c = getchar();bool flag = true;
    while( c < '0' || c > '9' ) {if(c == '-') flag = false;c = getchar();}
    while( c >= '0' && c <= '9' ) sum = sum * 10 + c - 48 , c = getchar();
    if(flag)  return sum;
     else return -sum;
}  
bool mycmp (node a,node b)
{
    return a.p < b.p;
}
void init()
{
    n = read();
    rep(i,1,n)
    {
        double x , p;
        scanf("%lf%lf",&x,&p);
        a[i].num = b[i].num = x;
        a[i].id = b[i].id = i;
        if(p != 0) a[i].p = 1.0/p;
        else a[i].p = (double) INF;
        p = 1.0 - p;
        if(p != 0) b[i].p = 1.0/p;
        else b[i].p = (double) INF;
    }
    sort(a + 1,a + n + 1,mycmp);
    sort(b + 1,b + n + 1,mycmp);
    return;
}
bool mycmp1(point a,point b)
{
    return a.x < b.x ||
      (a.x == b.x && a.y < b.y);
}
int s[1001000] , top;
void pre()
{  
    rep(i,1,n)
    {
        tmp[i].x = sum_b[i];
        tmp[i].y = lose_b[i];
    }
    sort(tmp + 1,tmp + n + 1,mycmp1);
    s[++top] = 1;
    rep(i,2,n)
    {
        while(top > 0 && tmp[i].y < tmp[s[top]].y) top--;
        s[++top] = i;
    }
    int st = 1;
    rep(i,st,top) 
        tmp[i-st+1].x = tmp[s[i]].x , tmp[i-st+1].y = tmp[s[i]].y;
    nn = top - st + 1;
    return;
}
void work()
{
    rep(i,1,n)
    {
        sum_a[i] += (a[i].num+sum_a[i-1]);
        if(a[i].p == a[i + 1].p) 
        {
            lose_a[i] = (double) INF;
            continue;
        }
        lose_a[i] = sum_a[i] * (a[i].p - 1);
    }
       rep(i,1,n)
    {
        sum_b[i] += (b[i].num+sum_b[i-1]);
        if(b[i].p == b[i + 1].p) 
        {
            lose_b[i] = (double) INF;
            continue;
        }
        lose_b[i] = sum_b[i] * (b[i].p - 1);
    }
    pre();
    return;
}
void solve()
{
    double Max = 0;
    rep(i,1,n)
    {
        int l = 1 , r = nn;
        while(l + 1 < r)
        {
            int mid = (l + r)/2;
            Max = max(Max,min(sum_a[i] - tmp[mid].y , tmp[mid].x - lose_a[i]));
            if(sum_a[i] - tmp[mid].y < tmp[mid].x - lose_a[i]) 
                r = mid;
            else l = mid;
        }
        Max = max(Max,min(sum_a[i] - tmp[l].y , tmp[l].x - lose_a[i]));
        Max = max(Max,min(sum_a[i] - tmp[r].y , tmp[r].x - lose_a[i]));
    }
    printf("%.6lf",Max);
    return;
}
int main()
{
    init();
    work();
    solve();
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值