我是从其他博客里看到这题的,上面说做法是wqs二分套wqs二分?但是我好懒呀,只用了一个wqs二分,于是(O(nlog^2n))→(O(n^2logn))
首先我们有一个(O(n^3))的暴力(DP),转移好写,形式优美,但复杂度不对
该怎样发现它的凸性质呢
1.打表√
2.冷静分析一波,每一种球肯定是越多越好,于是我们先固定选择(a)个普通球,然后那(b)个大师球肯定是从大到小挑选。这样的话每多选一个,新增的收益就会下降一点,也就是说这是个上凸函数。(口胡如果假的话,就锤我吧)√
然后就可以用wqs二分干掉一维啦,设(f[i][j])表示考虑到第(i)只精灵,已经选了(j)个普通球和若干个大师球时的最大期望,二分一个斜率(mid),每次check时转移一下(f)数组,拿最优决策点和(b)比较来调整范围
细节就是如果有多个相同的(f)值,取大师球最多的
代码如下:
#include <bits/stdc++.h>
using namespace std;
//暴力DP:n^3
//wqs二分:n^2logn
#define N 2000
const double eps = 1e-8; //我1e-6的精度被卡掉了。。。
int n, a, b;
double p[N+5], u[N+5], mid;
double sum;
int cnt;
bool dcmp(double x, double y) {
return fabs(x-y) <= eps;
};
struct Data {
double v;
int cnt;
bool operator < (const Data &rhs) const { //方便
return dcmp(v, rhs.v) ? cnt < rhs.cnt : v < rhs.v;
}
}f[N+5][N+5], opt;
Data newData(Data &d, double v, int cnt) {
return Data{d.v+v, d.cnt+cnt};
}
void check() {
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= a; ++j) {
//分类讨论转移
//1.什么都不选
//2.只选p
//3.只选u
//4.两个都选
//取max
f[i][j] = f[i-1][j];
if(j >= 1) f[i][j] = max(f[i][j], newData(f[i-1][j-1], p[i], 0)); //记得减掉附加权值
f[i][j] = max(f[i][j], newData(f[i-1][j], u[i]-mid, 1));
if(j >= 1) f[i][j] = max(f[i][j], newData(f[i-1][j-1], p[i]+u[i]-p[i]*u[i]-mid, 1));
}
}
opt = f[n][a];
}
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", &u[i]);
double l = 0, r = 1, slope = r;
while(fabs(r-l) > eps) { //二分斜率
mid = (l+r)*0.5;
check();
if(opt.cnt >= b) l = mid, slope = mid;
else r = mid;
}
mid = slope;
check();
printf("%.5lf
", opt.v+b*slope);
return 0;
}