题目大意
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);
}
}