【GDOI2017模拟8.15】Buy

本文介绍了一个涉及概率背包问题的解决方案,通过预处理最小花费矩阵、采用折半搜索策略及二分查找来高效解决购买特定数量物品的期望问题。

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

Description

有n个袋子和m个粮食(量词鬼畜,我是搬运工233)
第i个袋子里会有pi的概率装vi的金币,有1-pi的概率装一颗钻石。
每个粮食需要ci的金币和di的钻石才可以购买。
求购买粮食的期望个数。
n,m<=30,ci,vi<=100000000

Solution

首先,我们很显然可以预处理出Fi,j表示用i个钻石买j个粮食所需要的最小金币数。
然后,显然对于n<=30的情况,我们不能直接搜索出每个状态。
那我们可以考虑折半搜索。
把前a个袋子的所有状态用三元组S(p,s,v)表示有p的概率,s颗钻石和v个金币。(psv雾
因为每个袋子里只会有1颗钻石,所以我们按照钻石的数量吧所有三元组分组。
每组的s都相同,按v值排序,并且求p的前缀和。(后面会说用处)
然后,对于后面n-a个的状态S(p,s,v),我们枚举在前面用了t的钻石,总共买了i个粮食,那么我们会满足以下不等式:

F[s+t,i]<=v+v<F[s+t,i+1]

其中v’表示在前面用的金币数。
显然我们可以得出v’的取值范围,然后二分出那些状态满足这个取值。
因为有前缀和,所以我们可以很轻易第求出p的和。
然后对答案贡献就可以了。

Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 35
#define M 250005
using namespace std;
typedef double db;
typedef long long ll;
const int inf=2139062143;
struct note{ll v;db p;}g[N][M];
bool cmp(note x,note y) {return x.v<y.v;}
int ty,n,m,x,y,a,v[N],num[N];
ll f[N][M];
db sum[N][M],p[N],ans;
void dfs(int x,ll V,int d,db P) {
    if (x>a) {g[d][++num[d]].v=V;g[d][num[d]].p=P;return;}
    dfs(x+1,V+(ll)v[x],d,P*p[x]);dfs(x+1,V,d+1,P*(1-p[x]));
}
int search(int x,ll v) {
    int l=1,r=num[x]+1;
    while (l<r) {
        int mid=(l+r)/2;
        if (g[x][mid].v<v) l=mid+1;
        else r=mid;
    }
    return l;
}
void find(int x,ll V,int d,db P) {
    if (x>n) {
        fo(t,0,a) fo(i,1,m) {
            int l=f[d+t][i]-V,r=f[d+t][i+1]-V;
            l=search(t,l);r=search(t,r)-1;
            ans+=(sum[t][r]-sum[t][l-1])*P*i;
        }
        return;
    }
    find(x+1,V+(ll)v[x],d,P*p[x]);find(x+1,V,d+1,P*(1-p[x]));
}
int main() {
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d%d",&n,&m);memset(f,127,sizeof(f));
        fo(i,0,n) f[i][0]=0;
        memset(num,0,sizeof(num));ans=0;
        fo(i,1,n) scanf("%d%lf",&v[i],&p[i]),p[i]/=100.0;
        fo(i,1,m) {
            scanf("%d%d",&x,&y);
            fd(j,n,y) fd(k,m,1) f[j][k]=min(f[j][k],f[j-y][k-1]+x);
        }
        a=n*2/3.0;dfs(1,0,0,1);
        fo(i,0,a) {
            g[i][++num[i]].v=inf;
            sort(g[i]+1,g[i]+num[i]+1,cmp);
            fo(j,1,num[i]) sum[i][j]=sum[i][j-1]+g[i][j].p;
        }
        find(a+1,0,0,1);
        printf("%.4lf\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值