[2018.10.25]题解 CF739E 【Gosha is hunting】
cf官方题解为数据结构维护贪心,时间复杂度\(O(n^2logn)\)。
但是这道题用wqs二分可以做到\(O(nlog^2n)\)。
第一次使用这个算法的时候甚至不知道它叫wqs二分。
有关wqs二分->wqs本人的课件
容易想到此题的\(O(nab)\)的做法,就是暴力dp。
暴力dp:
#include<bits/stdc++.h>
using namespace std;
int n,a,b;
double p[2010],q[2010],dp[510][510][510];//dp[i][j][k]表示前i个神奇宝贝使用了j个精灵球和k个超级球
int main(){
scanf("%d%d%d",&n,&a,&b);
for(int i=1;i<=n;i++){
scanf("%lf",&p[i]);
}
for(int i=1;i<=n;i++){
scanf("%lf",&q[i]);
}
for(int i=1;i<=n;i++){
for(int j=0;j<=a;j++){
for(int k=0;k<=b;k++){
dp[i][j][k]=0;
if(max(j,k)>n){
continue;
}
dp[i][j][k]=dp[i-1][j][k];
if(j!=0){
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+p[i]);
}
if(k!=0){
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+q[i]);
}
if(j!=0&&k!=0){
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k-1]+p[i]+q[i]-p[i]*q[i]);
}
}
}
}
printf("%.10lf\n",dp[n][a][b]);
return 0;
}
我们考虑优化这个DP。
首先要优化状态,因为它本身三维状态。
我们试着不考虑a的限制,设状态\(dp_{i,j}\)表示前i个神奇宝贝,使用j个超级球和若干精灵球的最大期望。
但是这样可能会多用一些精灵球。于是我们假设每次使用一个精灵球,需要花费一个代价\(ca\),并在dp时记录精灵球的用量,设为\(ua\)。那么实际期望就是\(dp_{i,b}+ua_n\times ca\)。
那么如何使它刚好使用a个精灵球呢?
发现精灵球用量随ca的增加而不增(不一定降)。
没错,二分ca。
于是,复杂度从\(O(n^3)\)下降为\(O(n^2logn)\),已经可以过了。
这就是wqs二分,二分一个附加费用使某一物品的使用量发生变化。
但是还可以优化。
既然a这一维可以被省略,b这一维为什么不可以呢?
于是我们用同样的方法设\(dp_i\)为前i只神奇宝贝,使用若干精灵球和超级球的期望,二分使用超级球的代价\(cb\)并记录dp过程中超级球的使用量\(ub\)。
那么,实际代价就是\(dp_n+ua_n\times ca+ub_n\times cb\)
代码:
#include<bits/stdc++.h>
using namespace std;
const double _=1e-10;
int n,a,b,numa[100010],numb[100010];
double p[100010],q[100010],dp[100010];
void Check(double ca,double cb){//当使用精灵球的代价为ca,超级球为cb时的期望
for(int i=1;i<=n;i++){
dp[i]=0;
numa[i]=0;
numb[i]=0;
dp[i]=dp[i-1];
numa[i]=numa[i-1];
numb[i]=numb[i-1];
if(dp[i]<dp[i-1]+p[i]-ca-_){//注意精度
dp[i]=dp[i-1]+p[i]-ca;
numa[i]=numa[i-1]+1;
numb[i]=numb[i-1];
}
if(dp[i]<dp[i-1]+q[i]-cb-_){
dp[i]=dp[i-1]+q[i]-cb;
numa[i]=numa[i-1];
numb[i]=numb[i-1]+1;
}
if(dp[i]<dp[i-1]+p[i]-ca+q[i]-cb-p[i]*q[i]-_){
dp[i]=dp[i-1]+p[i]-ca+q[i]-cb-p[i]*q[i];
numa[i]=numa[i-1]+1;
numb[i]=numb[i-1]+1;
}
}
}
double check(double co){
double l=0,r=1,mid;//二分cb
for(int i=1;i<=50;i++){//同二分ca的过程
mid=(l+r)/2;
Check(co,mid);
if(numb[n]>b){
l=mid;
}else{
r=mid;
}
}
return l;
}
double l,r,mid,v,ans;
int main(){
scanf("%d%d%d",&n,&a,&b);
for(int i=1;i<=n;i++){
scanf("%lf",&p[i]);
}
for(int i=1;i<=n;i++){
scanf("%lf",&q[i]);
}
l=0;
r=1;//二分ca
for(int i=1;i<=50;i++){//由于实数二分所以二分固定次数即可
mid=(l+r)/2;
v=check(mid);
if(numa[n]>a){//用量大于a,增大ca
l=mid;
}else{//否则减小ca
r=mid;
}
}
Check(l,v);
printf("%.5lf\n",dp[n]+l*a+v*b);
return 0;
}