一次函数优化

【线上训练 17】旅行

(http://zhengruioi.com/problem/1192)
有一件事,有 \(n\) 种方法可以完成,对于第 \(i\) 种方法有 \(cnt_i\) 个步骤,每个步骤都有一个失败的概率 \(p_{i,j}\),这些步骤可以按任意顺序进行,但如果一个步骤失败了,那这种方法就无法成功了。\(n\) 种方法相互独立互不影响,且若要完成这件事则必须某一种方法中的每个步骤都要成功。当你发现这件事完成了,或是发现这件事不可能完成了(注意不一定要做完每个步骤),你才可以停下。你现在希望最小化做的步骤数,求这个值的期望。\(n\leq 10^5,\sum_{i=1}^n cnt_i \leq 10^6\) (题面经过一定改动)

sol:

首先考虑如果 \(n = 1\),那么答案是多少。显然小 \(D\) 应该按照失败概率从大到小去查询,因为失败一个就可以停下来了,我们要停得尽量早,所以应该先查询失败概率大的。
不妨设 \(p_{i,j}\) 是按照从大到小的顺序排好序的。显然每次做一个方法一定不会做到一半之后去做另一个,因为这样前者不会带来任何收益。所以我们可以假设做的顺序是 \(c_1, c_2, · · · , c_n\)
那么我们设 \(E_i\) 表示只做 \(c_i\), \(c_{i+1}, · · · , c_n\) 需要的步数的期望,则有
\(E_i = p_{c_i,1}(1+E_{i+1})+(1−p_{c_i,1})p_{c_i,2}(2+E_{i+1})+ (1−p_{c_i,1})(1−p_{c_i,2})p_{c_i,3}(3+E_{i+1})+· · · + (1−p_{c_i,1})(1−p_{c_i,2})· · · (p_{c_i,cnt_i})cnt_i\)
于是我们可以把它写成 \(E_i = k_{c_i} E_{i+1} + b_{c_i}\) 的形式。因此,我们相当于有一堆一次函数,我们现在要把它们嵌套起来,最小化最后的结果。
考虑两个一次函数 \(y = a_i x + b_i\) 以及 \(y = a_j x + b_j\),如果 \(a_i(a_j x + b_j ) + b_i ≤ a_j (a_i x + b_i) + b_j\),那么有 \(\frac{a_{i−1}} {b_i} ≤ \frac{a_{j−1}} {b_j}\)。于是按照 \(\frac{a_i−1} {b_i}\) 排序后嵌套即可。
但值得一提的是,若一种方法有一步不可能完成,那这种方法整个就不需要考虑了。
时间复杂度:\(O(\sum_{i=1}^n cnt_i + n \log n)\)

#include <bits/stdc++.h>
#define ld long double
using namespace std;
const int N=505;
int n,cnt;ld ans,tmp,st[N];
struct node{ld k,b;} p[N];
inline bool cmp(const node x,const node y){return (x.k-1)/x.b<(y.k-1)/y.b;}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&cnt);tmp=1.0;
        for(int j=1;j<=cnt;j++) scanf("%Lf",&st[j]);
        sort(st+1,st+1+cnt);
        if(st[cnt]>=1-1e-9){i--;n--;continue;}
        reverse(st+1,st+1+cnt);
        while(st[cnt]<=1e-9) cnt--;
        for(int j=1;j<=cnt;j++)
            p[i].k+=st[j]*tmp,p[i].b+=st[j]*tmp*j,tmp*=(1-st[j]);
        p[i].b+=tmp*cnt;
    }
    sort(p+1,p+1+n,cmp);
    for(int i=n;i>0;i--) ans=p[i].k*ans+p[i].b;
    printf("%.10Lf\n",ans);
    return 0;
}


猜你喜欢

转载自www.cnblogs.com/zxynothing/p/11824370.html