题目链接
首先一个很显然的想法就是直接\(DP\)
设\(f[i][j][k]\)表示抓前\(i\)个神奇宝贝用了\(j\)个宝贝球和\(k\)个超级球,有:
\[f[i][j][k] = max (f[i - 1][j - 1][k] + p[i], f[i - 1][j][k - 1] + u[i], f[i - 1][j - 1][k - 1] + p[i] + u[i] -p[i] * u[i]) \]
这样直接转移是\(O(n^3)\)的。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define R register
#define LL long long
const int inf = 0x3f3f3f3f;
const int N = 200 + 5;
inline int read() {
char a = getchar(); int x = 0,f = 1;
for(;a > '9' || a < '0';a = getchar()) if(a == '-') f = -1;
for(;a >= '0' && a <= '9'; a = getchar()) x = x * 10 + a - '0';
return x * f;
}
int n, a, b;
double p[N], u[N];
double f[N][N][N];
int main() {
freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
n = read(); a = read(); b = read();
for(R int i = 1; i <= n; i ++) scanf("%lf", & p[i]);
for(R int i = 1; i <= n; i ++) scanf("%lf", & u[i]);
for(R int i = 1; i <= n; i ++) {
for(R int j = 0; j <= a; j ++)
for(R int k = 0; k <= b; k ++) {
f[i][j][k] = f[i - 1][j][k];
if(j != 0) f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k] + p[i]);
if(k != 0) f[i][j][k] = max(f[i][j][k], f[i - 1][j][k - 1] + u[i]);
if(j != 0 && k != 0) f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k - 1] + p[i] + u[i] - p[i] * u[i]);
}
}
printf("%lf\n", f[n][a][b]);
return 0;
}
考虑优化,打表或者脑补可以发现,当\(n, b\)固定的时候\(f[n][a][i]\)是一个下凸的函数,而且最低点是\(f[n][a][0]\),其实脑补一下也就知道用的球越多,这个期望肯定越大,所以就有这么个性质。考虑用wqs二分进行优化。对于每一个超级球都带上一个费用,通过二分这个费用来控制宝贝球的使用个数,可以做到\(n^2log_2n\) (大概的,实际二分多少次看过了就行)。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define R register
#define LL long long
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
inline int read() {
char a = getchar(); int x = 0,f = 1;
for(;a > '9' || a < '0';a = getchar()) if(a == '-') f = -1;
for(;a >= '0' && a <= '9'; a = getchar()) x = x * 10 + a - '0';
return x * f;
}
int n, a, b;
double p[N], u[N];
double f[N][N];
int cnt[N][N];
inline bool check(double mid) {
memset(f, 0, sizeof(f));
memset(cnt, 0, sizeof(cnt));
for(R int i = 1; i <= n; i ++) {
for(R int j = 0; j <= a; j ++) {
cnt[i][j] = cnt[i - 1][j];
f[i][j] = f[i - 1][j];
if(j != 0 && f[i - 1][j - 1] + p[i] >= f[i][j]) {
f[i][j] = f[i - 1][j - 1] + p[i];
cnt[i][j] = cnt[i - 1][j - 1];
}
if(j != 0 && f[i - 1][j - 1] + u[i] + p[i] - u[i] * p[i] - mid >= f[i][j]) {
f[i][j] = f[i - 1][j - 1] + u[i] + p[i] - u[i] * p[i] - mid;
cnt[i][j] = cnt[i - 1][j - 1] + 1;
}
if(f[i - 1][j] + u[i] - mid >= f[i][j]) {
f[i][j] = f[i - 1][j] + u[i] - mid;
cnt[i][j] = cnt[i - 1][j] + 1;
}
}
}
return cnt[n][a] <= b;
}
int main() {
freopen("a.in","r",stdin);
freopen("b.out","w",stdout);
n = read(); a = read(); b = read();
for(R int i = 1; i <= n; i ++) scanf("%lf", & p[i]);
for(R int i = 1; i <= n; i ++) scanf("%lf", & u[i]);
double l = 0, r = 1;
for(R int i = 1; i <= 60; i ++) {
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.5lf\n", f[n][a] + l * b);
return 0;
}
但是实际上发现,不仅仅对于\(b\),\(a\)也有这个性质,所以可以再套一个\(wqs\),二分一个宝贝球的费用,可以做到\(nlog_2n^2\)(这个也是假的)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define R register
#define LL long long
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const double eps = 1e-9;
inline int read() {
char a = getchar(); int x = 0,f = 1;
for(;a > '9' || a < '0';a = getchar()) if(a == '-') f = -1;
for(;a >= '0' && a <= '9'; a = getchar()) x = x * 10 + a - '0';
return x * f;
}
int n, a, b;
double p[N], u[N];
double f[N];
int cnt1[N], cnt2[N];
inline bool Check(double v1,double v2) {
memset(f, 0, sizeof(f));
memset(cnt1, 0, sizeof(cnt1));
memset(cnt2, 0, sizeof(cnt2));
for(R int i = 1;i <= n; i ++) {
f[i] = f[i - 1];
cnt1[i] = cnt1[i - 1];
cnt2[i] = cnt2[i - 1];
if(f[i - 1] + p[i] - v1 - eps > f[i]) {
f[i] = f[i - 1] + p[i] - v1;
cnt1[i] = cnt1[i - 1] + 1;
cnt2[i] = cnt2[i - 1];
}
if(f[i - 1] + u[i] - v2 - eps > f[i]) {
f[i] = f[i - 1] + u[i] - v2;
cnt1[i] = cnt1[i - 1];
cnt2[i] = cnt2[i - 1] + 1;
}
if(f[i - 1] + p[i] + u[i] - p[i] * u[i] - v1 - v2 - eps > f[i]) {
f[i] = f[i - 1] + p[i] + u[i] - p[i] * u[i] - v1 - v2;
cnt1[i] = cnt1[i - 1] + 1;
cnt2[i] = cnt2[i - 1] + 1;
}
}
return cnt2[n] <= b;
}
double tl;
inline bool check(double md) {
double l = 0, r = 1;
for(R int i = 1; i <= 50; i ++) {
double mid = (l + r) / 2;
if(Check(md, mid)) r = mid;
else l = mid;
}
tl = l;
return cnt1[n] <= a;
}
int main() {
//freopen("a.in","r",stdin);
//freopen("b.out","w",stdout);
n = read(); a = read(); b = read();
for(R int i = 1; i <= n; i ++) scanf("%lf", & p[i]);
for(R int i = 1; i <= n; i ++) scanf("%lf", & u[i]);
double l = 0, r = 1;
for(R int i = 1; i <= 50; i ++) {
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
//printf("%d %d\n", cnt1[n], cnt2[n]);
printf("%.5lf\n", f[n] + l * a + tl * b);
return 0;
}
但是这个写法有一个巨大的坑点就是会被卡精度,一定要在代码中减去\(eps\),这是因为我们要在判断的时候尽量少使用球,这样才可以碰到最优解。要是用了和没有用没有区别的话,不用显然会更加优秀,也是题目所要求的。