[CC LEBOXES]buy

题目大意

n个袋子,第i个袋子有pi几率装有vi金钱,否则装有一颗宝石。
有m个食物,第i个食物需要ci的金钱和di颗宝石,求最优情况下,期望买下食物个数。

折半

宝石个数不超过n。
设f[i,j]表示买下i个食物使用了至多j颗宝石最少花费的钱,可以dp出来。
然后对于袋子,我们折半搜索,先搜索前半部分,并根据宝石数分段,每段按照金钱排序,并统计概率前缀和。
然后搜索后半部分,假如搜出的状态是(v,d,p)代表有v的金钱d颗宝石,发生该场面的概率为p。
枚举前半部分获得的宝石数t以及最终买下的食物个数i。
假设前半部分的金钱为g,那么要有
f[i,t+d]<=v+g

#include<cstdio> 
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--) 
using namespace std;
typedef double db;
const int maxn=30+10,inf=1000000000;
int f[maxn][maxn],v[maxn],c[maxn],d[maxn],ll[maxn],rr[maxn];
struct dong{
    int v,d;
    db p;
} a[1000000];
db p[maxn],sum[1000000],ans;
int i,j,k,l,r,t,n,m,mid,top,ca;
void dfs(int x,int y,int z,db q){
    if (x==mid+1){
        a[++top].v=y;
        a[top].d=z;
        a[top].p=q;
        return;
    }
    dfs(x+1,y+v[x],z,q*p[x]);
    dfs(x+1,y,z+1,q*(1-p[x]));
}
void work(int v,int d,db p){
    int i,t,j,k,l,r,x,y,mid;
    fo(i,1,n)
        fo(t,0,n){
            if (!ll[t]) continue;
            j=f[i][t+d]-v;
            k=f[i+1][t+d]-v;
            if (j>0&&j<1000){
                l=l;
            }
            if (a[rr[t]].v<j||a[ll[t]].v>=k) continue;
            l=ll[t];r=rr[t];
            while (l<r){
                mid=(l+r)/2;
                if (a[mid].v>=j) r=mid;else l=mid+1;
            }
            x=l;
            l=ll[t];r=rr[t];
            while (l<r){
                mid=(l+r+1)/2;
                if (a[mid].v<k) l=mid;else r=mid-1;
            }
            y=l;
            ans=ans+(sum[y]-sum[x-1])*i*p;
        }
}
void dg(int x,int y,int z,db q){
    if (x==n+1){
        work(y,z,q);
        return;
    }
    dg(x+1,y+v[x],z,q*p[x]);
    dg(x+1,y,z+1,q*(1-p[x]));
}
bool cmp(dong a,dong b){
    return a.d<b.d||(a.d==b.d&&a.v<b.v);
}
int main(){
    //freopen("t2.in","r",stdin);freopen("t2.out","w",stdout);
    scanf("%d",&ca);
    while (ca--){
        scanf("%d%d",&n,&m);
        fo(i,1,n){
            scanf("%d%d",&v[i],&t);
            p[i]=(db)t/100;
        }
        fo(i,1,m) scanf("%d%d",&c[i],&d[i]);
        memset(f,127/2,sizeof(f));
        fo(i,0,n) f[0][i]=0;
        fo(i,1,m)
            fd(j,m,1)
                fo(l,d[i],n) f[j][l]=min(f[j][l],f[j-1][l-d[i]]+c[i]);
        mid=n/2;
        top=0;
        dfs(1,0,0,1);
        sort(a+1,a+top+1,cmp);
        fo(i,0,n) ll[i]=0;
        ll[a[1].d]=1;
        fo(i,2,top)
            if (a[i].d!=a[i-1].d){
                rr[a[i-1].d]=i-1;
                ll[a[i].d]=i;
            }
        rr[a[top].d]=top;
        sum[0]=0;
        fo(i,1,top) sum[i]=sum[i-1]+a[i].p;
        ans=0;
        dg(mid+1,0,0,1);
        printf("%.4lf\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值