codeforces739E. Gosha is hunting

Description
Gosha is hunting. His goal is to catch as many Pokemons as possible. Gosha has a Poke Balls and b Ultra Balls. There are n Pokemons. They are numbered 1 through n. Gosha knows that if he throws a Poke Ball at the i-th Pokemon he catches it with probability pi. If he throws an Ultra Ball at the i-th Pokemon he catches it with probability ui. He can throw at most one Ball of each type at any Pokemon.

The hunting proceeds as follows: at first, Gosha chooses no more than a Pokemons at which he will throw Poke Balls and no more than b Pokemons at which he will throw Ultra Balls. After that, he throws the chosen Balls at the chosen Pokemons. If he throws both Ultra Ball and Poke Ball at some Pokemon, he is caught if and only if he is caught by any of these Balls. The outcome of a throw doesn’t depend on the other throws.

Gosha would like to know what is the expected number of the Pokemons he catches if he acts in an optimal way. In other words, he would like to know the maximum possible expected number of Pokemons can catch.

Input
The first line contains three integers n, a and b (2 ≤ n ≤ 2000, 0 ≤ a, b ≤ n) — the number of Pokemons, the number of Poke Balls and the number of Ultra Balls.
The second line contains n real values p1, p2, …, pn (0 ≤ pi ≤ 1), where pi is the probability of catching the i-th Pokemon if Gosha throws a Poke Ball to it.
The third line contains n real values u1, u2, …, un (0 ≤ ui ≤ 1), where ui is the probability of catching the i-th Pokemon if Gosha throws an Ultra Ball to it.
All the probabilities are given with exactly three digits after the decimal separator.

Output
Print the maximum possible expected number of Pokemons Gosha can catch. The answer is considered correct if it’s absolute or relative error doesn’t exceed 10 - 4.

Examples
input
3 2 2
1.000 0.000 0.500
0.000 1.000 0.500
output
2.75
input
4 1 3
0.100 0.500 0.500 0.600
0.100 0.500 0.900 0.400
output
2.16
input
3 2 0
0.412 0.198 0.599
0.612 0.987 0.443
output
1.011

题目大意:现在有n个神奇宝贝,而你有a个普通球和b个高级球,每个神奇宝贝可以用一个普通球或一个高级球或两个球同时使用(不要理解为字面意思,捕捉成功的概率并没有大小相对关系),成功概率为p[i],u[i],p[i]+u[i]−p[i]∗u[i],不能对一个神奇宝贝多次尝试捕捉,求期望最大捕捉数。

非常有趣的一道题(题解学习自zky学长)(在shallwe的要求下加上他的博客中的题解)(听说杜教当场用这个方法A了)。
根据期望的线性性,我们可以计算在总体满足a和b的限制下每个神奇宝贝被捕捉的概率。所以对于每一个神奇宝贝来说就是阶段啦。
首先当然是简单DP:设 f[i][j][k] 表示现在到了第 i 个神奇宝贝,已经使用了j个普通球和 k 个高级球,此时的期望最大捕捉数。转移也非常简单:
f[i][j][k]=max(f[i1][j][k],f[i1][j1][k]+p[i],f[i1][j][k1]+u[i],f[i1][j1][k1]+p[i]+u[i]p[i]u[i])
当然这个 O(n3) 的DP并不是正解。。。(UPD:CF的评测机真是吓哭我了,这个方法实测能A)
这里就有一个神奇的方法来优化了。假如我们有一个凸函数 f(x) ,然后一只神奇的小鸟会告诉我们它的极值以及极值点(我们随便扔给它一个凸函数,它都会告诉我们),除此之外我们就什么都不知道啦,那么我们要如何求任意值 f(x0) 呢?我们考虑构造一个新函数 F(i)=f(i)kx ,求导(或直观感受)可知 F(i) 仍然是一个凸函数,并且随着 k 的单调增加,极值点会单调向左移动!单调?二分!我们二分出一个k使得 F(x) 极值在我们想要的 x0 处取得,然后我们就可以通过神奇的小鸟知道 f(x0)=F(x0)+kx0
回到本题,我们发现 f[i][j](k) 实际上就是一个凸函数(更进一步说,它似乎其实是一个单调递增函数?),利用上文中的方法进行转化,我们观察转移方程,由于总体上是取 max ,我们可以发现函数 f[i][j] 的极值只跟 f[i1][j],f[i1][j1] 的极值有关,而且又考虑到我们只需知道在当前我们二分出来的 k f[n][a]的极值和极值点是多少,所以我们根据极值就可以直接 O(n2) 转移了。
似乎说的并不是很明白,请结合代码体会一下。。。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2010;
int n,a,b;
double p[N],u[N],q[N];
double f[N][N];int g[N][N];
#define eps 1e-9
#define nowf (f[i][j])
#define nowg (g[i][j])
int dcmp(double x)
{
    if(fabs(x)<eps)return 0;
    return ((x>0)?1:-1);
}
void up(double &f1,int &g1,double f2,int g2)
{
    if(f2>f1+eps){f1=f2;g1=g2;}
}
int get(double K)
{
    for (int i=1;i<=n;++i)
        for (int j=0;j<=a;++j)
        {
            nowf=nowg=0;
            up(nowf,nowg,f[i-1][j],g[i-1][j]);
            up(nowf,nowg,f[i-1][j]+u[i]-K,g[i-1][j]+1);
            if(j)up(nowf,nowg,f[i-1][j-1]+p[i],g[i-1][j-1]);
            if(j)up(nowf,nowg,f[i-1][j-1]+q[i]-K,g[i-1][j-1]+1);
        }
    return g[n][a];
}
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    for (int i=1;i<=n;++i)scanf("%lf",p+i);
    for (int i=1;i<=n;++i)scanf("%lf",u+i);
    for (int i=1;i<=n;++i)q[i]=p[i]+u[i]-p[i]*u[i];
    double l=-1e4,mid,r=1e4;
    while(r-l>eps)
    {
        mid=(l+r)*0.5;
        if(get(mid)<b)r=mid;
        else l=mid;
    }
    printf("%lf\n",f[n][a]+l*b);
    return 0;
}

后记:
为什么总感觉这种方法使用范围有一定局限性?它实际上利用了转移的几个性质:
(1)状态中 f[i][j] 关于 k 是个凸函数。
(2)转移方程中只有取max操作,所以只需要保存 f[i][j] 的极值和极值点直接转移,从而优化为 O(n2)
其实从本质上来说,这种方法就是构造出来了一个新函数,通过调整参数 k <script type="math/tex" id="MathJax-Element-28">k</script>的大小把函数逐渐调整到我们需要的值上(根据什么“同增异减”法则或是一些特殊奇怪的函数我们似乎可以把这种方法拓展到一些其它函数上?果然非常有趣啊)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值