YC339A [ 20240915 CQYC NOIP 模拟赛 T1 ] 演讲(talk)
题意
有 \(n\) 个地点,你可以:
- 使用 \(\frac{a_i}{len}\) 的代价标记该地点。
- 使用 \(\frac{b_i}{len}\) 的代价标记该地点并使得 \(len := len + 1\)。
- 跳过该地点。
你不需要按照顺序标记,问标记 \(m\) 个点的最小代价是多少(可以证明答案是实数)。
\(n \le 500, a_i \le b_i\)。
Sol
注意到一个贪心,就是枚举第二种操作选了多少,然后剩下就贪心选择第一种操作。
这样为什么是错的?因为可以有情况使得一个地点 \(a_i\) 极小而 \(b_i\) 需要选择,这个时候只需要选择 \(a_i\),换成下一个不是很优的 \(b_j\)。
但是注意到这个东西我们可以考虑 \(\texttt{dp}\),依然是枚举第二种操作的数量,设 \(f_{i, j, k}\) 表示前 \(i\) 个地点第一种操作的数量为 \(j\) 第二种操作的数量为 \(k\)。
这样是 \(O(n ^ 4)\) 的,很菜。
考虑一个优化,不难发现最后一次选择的第二种操作之前不会有地点选择第三种操作,也就是被跳过。
于是只需要枚举最后一次的位置就行了,改设为 \(f_{i, j}\) 前 \(i\) 个地点选择了 \(j\) 个第二种操作。
复杂度 \(O(n ^ 3)\)。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <array>
#define pii pair <int, int>
#define db double
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;
#endif
int read() {
int p = 0, flg = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flg = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
p = p * 10 + c - '0';
c = getchar();
}
return p * flg;
}
void write(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
bool _stmer;
#define fi first
#define se second
const int N = 505, inf = 2e9;
array <array <double, N>, N> f;
array <pii, N> s;
array <array <pii, N>, N> g;
bool _edmer;
int main() {
cerr << (&_stmer - &_edmer) / 1024.0 / 1024.0 << "MB\n";
#ifndef cxqghzj
freopen("talk.in", "r", stdin);
freopen("talk.out", "w", stdout);
#endif
int n = read(), m = read();
for (int i = 1; i <= n; i++) {
s[i].fi = read(), s[i].se = read();
if (!~s[i].se) s[i].se = inf;
}
sort(s.begin() + 1, s.begin() + 1 + n, [](pii x, pii y) {
return x.se < y.se;
});
for (int i = 0; i <= n; i++)
g[i] = s, sort(g[i].begin() + 1 + i, g[i].begin() + 1 + n);
#define upd(x, y) (x = min(x, y))
db ans = 0;
for (int i = 1; i <= m; i++)
ans += g[0][i].fi;
for (int l = 1; l <= m; l++) {
for (int i = 0; i <= n; i++)
f[i].fill(4e18);
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= l; j++) {
upd(f[i][j], f[i - 1][j] + (db)s[i].fi / (l + 1));
if (j) upd(f[i][j], f[i - 1][j - 1] + (db)s[i].se / j);
}
if (i < l) continue;
db sum = 0;
for (int j = i + 1; j <= m; j++)
sum += (db)g[i][j].fi / (l + 1);
ans = min(ans, sum + f[i][l]);
}
}
printf("%.6lf\n", ans);
return 0;
}