Buy(CC LEBOXES)

题目大意

给出n个袋子和m个粮食,每个袋子有p[i]%的概率获得v[i]个金币,有1-p[i]%的概率获得1个钻石,而购买一个粮食要付出c[i]个金币和d[i]个钻石。求购买粮食的期望个数。
n,m<=30 v[i],c[i]<=10^7,p[i]<=100

注意到n很小,也就是钻石数很小

设f[i][j]表示拥有钻石数为j时,购买i个粮食所需的最小金币代价,这是个经典背包问题,可以O(n^3)解决。
如果直接暴力所有袋子的状态来计算期望,复杂度是O(n*2^n)。

可以考虑折半搜索。

先暴力处理前a个袋子的状态。
dfs(i,v,z,p)表示当前取到第i个(第i个袋子的状态还未知),当前有v个金币和z个钻石,此时的概率为p。
那么当i>a时,我们就退出dfs,并把dfs(a+1,v,z,p)的状态保存下来,t[z]表示拥有z个钻石的状态的个数,a[z][j].v表示拥有z个钻石的状态中第j个状态的金币数,a[z][j].p表示拥有z个钻石的状态中第j个状态的概率值,这样我们就把前a个袋子的状态都保存下来了。
然后对于所有的a[i]以a[i][j].v为关键字从小到大排序,排序后再处理a[i][j].s表示a[i][1~j].p的和。
dfs的复杂度为(2^a),排序的复杂度为O(2a*2^a)
然后再dfs一次处理剩余袋子的状态。
设结束状态为(v,z,p)(意义相同),计算贡献:
枚举购买的粮食数i,枚举前a个袋子获得的钻石数记作z1,前a个袋子获得的金币数记作v1,当然v1时不能枚举的,因为 f[i][z+z1]<=v+v1<f[i+1][z+z1] ,所以可以二分出v1在a[z1]的位置范围l~r,总概率就等于p*(a[z1][r].s-a[z1][l-1].s),期望贡献为总概率*i。
设b=n-a,后半部分的复杂度为O(m*a^2*2^b)(这里把二分的复杂度估计为a,即所有t[z1]=2^a,实际达不到)
当a=2n/3时复杂度为O( 4n322n3+m4n292n3 )这个复杂度已经可以通过所有测试数据了。

代码

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=30+5;const int ma=1048576;
int i,j,n,t,m,l,n1,n2;
int f[maxn][maxn],v[maxn],p[maxn],c[maxn],d[maxn];
int z[maxn];
struct ar{
    int x;double p,sum;
} a[21][ma];
double ans;
void dfs(int x,int y,int y1,double s){
    if (x>n1){
        a[y1][++z[y1]].x=y,a[y1][z[y1]].p=s;
        return;
    }double s1=(double)p[x]/100;
    dfs(x+1,y+v[x],y1,s*s1),dfs(x+1,y,y1+1,s*(1-s1));
}
int fen(int t,int x){
    int l=1,r=z[t];
    if (a[t][r].x<x) return r+1;
    while (l<r){
        int m=(l+r)/2;
        if (a[t][m].x>=x) r=m;else l=m+1;
    }return l;
}
void df(int x,int y,int y1,double s){
    if (x>n){
        int t,i;
        fo(t,0,n1){
            fo(i,1,m) if (f[i][t+y1]<f[m+1][0]){
                int d=t+y1;
                int s1=f[i][d]-y,s2=f[i+1][d]-y;if (s2<=0) continue;
                int l=fen(t,s1),r=fen(t,s2);r--;
                ans+=(double)i*s*(a[t][r].sum-a[t][l-1].sum);
            }else break;
        }
        return;
    }double s1=(double)p[x]/100;
    df(x+1,y+v[x],y1,s*s1),df(x+1,y,y1+1,s*(1-s1));
}
bool cmp(ar x,ar y){
    return x.x<y.x;
}
int main(){
    scanf("%d",&t);
    while(t){
        t--;
        scanf("%d%d",&n,&m);
        fo(i,1,n) scanf("%d%d",&v[i],&p[i]);
        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)
        fod(j,m,1)
        fo(l,d[i],n) f[j][l]=min(f[j][l],f[j-1][l-d[i]]+c[i]);
        ans=0;
        n1=n/3*2;
        memset(z,0,sizeof(z));
        dfs(1,0,0,1);
        fo(i,0,n1) {
            sort(a[i]+1,a[i]+1+z[i],cmp);
            fo(j,1,z[i]) a[i][j].sum=a[i][j-1].sum+a[i][j].p;
        }
        df(n1+1,0,0,1);
        printf("%.4lf\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值