CSP-S 模拟赛 10
T1 洛谷 U490727 返乡
思路
首先要意识到一个问题,就是如果所有人总分一定,那么是不会出现偏序的。
可以感性理解一下,就是对于 \(i, j\), 若 \(a_i \leq a_j, b_i \leq b_j\),那么一定会有 \(c_i \geq c_j\)。然后枚举方案时,保证不会出现 \(a_i == a_j \& b_i == b_j\) 这种情况就行。
然后就是看在总分为多少时,方案数最多。
考试的时候可以先写一个暴力程序,先枚举总分 \(s \in [0, 3n]\),然后枚举前两科分数 \(i, j \in [0, n]\),若算下来第三科成绩 \(s - i - j\) 也小于等于 \(n\),那就是一种合法情况,记录下方案数,最后求最大值。
复杂度 \(O(3n^3)\),代码如下:
#include <bits/stdc++.h>
#define mkpr make_pair
#define fir first
#define sec second
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
const int inf = 0x3f3f3f3f;
int n, ansc, anss;
int ans[maxn][3];
int main() {
scanf("%d", &n);
for (int s = 0; s <= 3 * n; ++s) {
int cnt = 0;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
if (i + j <= s && s - i - j <= n) ++cnt;
if (cnt > ansc) {
anss = s; // 记录下方案最多时的总分,找找规律
ansc = cnt;
cnt = 0;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
if (i + j <= s && s - i - j <= n) {
++cnt,
ans[cnt][0] = i;
ans[cnt][1] = j;
ans[cnt][2] = s - i - j;
}
}
}
printf("anss:%d, ansc:%d\n", anss, ansc);
for (int i = 1; i <= ansc; ++i)
printf("%d %d %d\n", ans[i][0], ans[i][1], ans[i][2]);
return 0;
}
\(n \leq 600\),由于有常数 \(3\),所以过不去。
然后会有规律:\(s = \frac{3n}{2}\) 时,方案数最多。(笔者有点不太会证,先鸽着。。。)
所以直接用如下代码:
#include <bits/stdc++.h>
#define mkpr make_pair
#define fir first
#define sec second
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
const int inf = 0x3f3f3f3f;
int n, ansc;
int ans[maxn][3];
int main() {
scanf("%d", &n);
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= n; ++j) {
int k = n * 3 / 2 - i - j;
if (k >= 0 && k <= n) {
++ansc;
ans[ansc][0] = i;
ans[ansc][1] = j;
ans[ansc][2] = k;
}
}
}
printf("%d\n", ansc);
for (int i = 1; i <= ansc; ++i)
printf("%d %d %d\n", ans[i][0], ans[i][1], ans[i][2]);
return 0;
}
\(O(n ^ 2)\) 水过。
T2 洛谷 U490729 连接
相当沟槽好的一道题,让我花了一天改。。。
还是要喷一下题解,高中肄业都写不出那样生理紊乱的表达。。。
还好有同机房的大佬给出的解法。
一、\(15pts\) 思路(\(n, l_i, p_i \leq 10,O((\sum_{i = 1}^{n}l_i)^2)\))
可以直接用数组把钢管模拟出来,处理出质量与长度的前缀和数组后,直接 \((\sum_{i = 1}^{n}l_i)^2\) 暴力枚举求密度。
代码如下:
int sl, sm[107];
double ans;
void Main() {
for (int i = 1; i <= n; ++i) {
for (int j = sl + 1; j <= sl + len[i]; ++j)
sm[j] = sm[j - 1] + p[i];
sl += len[i];
}
for (int i = 1; i <= sl; ++i) {
for (int j = i; j <= sl; ++j) {
if (sm[j] - sm[i - 1] >= L && sm[j] - sm[i - 1] <= R)
ans = max(ans, 1.0 * (sm[j] - sm[i - 1]) / (j - i + 1));
}
}
printf("%.10lf\n", ans);
}
二、\(50pts\) 思路(\(n \leq 5000, O(n^2)\))
\(n^2\) 的做法要求我们以完整的块为单位枚举求解。
思考两种截取方法,一种是截整块(即若干个连续的块完整地截下来),另一种是带散块(即两端的钢管不会被截完)。
对于前者,依旧用前缀和直接 \(O(n^2)\) 求解。对于后者,要意识到两件事:
- 最多只会有一端是散块,不会两端都是散块:因为在质量允许的范围内,我们肯定更倾向于去两端中密度更大的那一个,那样可以使整体密度更大(这点根据生活经验或者糖水不等式);
- 散块的质量只会有 \(L\) 或 \(R\):因为如果散块的密度大,我们肯定希望多取,那就直接取到 \(R\),可以使整体密度最大化。如果密度小,那我们肯定希望少取,那就直接取到 \(L\),防止其继续拉低整体密度。
思路大概如此,实现时由于散块可能是两端中的任意一个,所以要给 \(l_i, p_i\) 数组反转后再求解一次。枚举左端点 \(i\),然后用双指针或二分查找维护右端点取值范围,再直接计算散块密度。
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int maxn = 3e5 + 7;
int n, len[maxn], p[maxn];
ll L, R, sm[maxn], sl[maxn];
db ans;
void solve() {
for (int i = 1; i <= n; ++i)
sl[i] = sl[i - 1] + len[i],
sm[i] = sm[i - 1] + 1ll * len[i] * p[i];
// printf("sl: ");
// for (int i = 1; i <= n; ++i) printf("%d ", sl[i]);
// printf("\n");
//
// printf("sm: ");
// for (int i = 1; i <= n; ++i) printf("%d ", sm[i]);
// printf("\n");
// 算一端是散块的情况
int l = 1, r = 0;
for (int i = 1; i <= n; ++i) {
// 必须满足质量大于 L
while (l <= n && sm[l] - sm[i - 1] < L) ++l;
// 这里 r 的判断条件必须是 r <= n 而不是 r + 1 <= n
// 因为后面要判断质量能不能取到 R
while (r <= n && sm[r + 1] - sm[i - 1] < R) ++r;
// 再怎么取都无法达到 L
if (l > n) break;
// 分子是质量, 分母是长度
db pl = 1.0 * L / (sl[l - 1] - sl[i - 1] + (L - (sm[l - 1] - sm[i - 1])) * 1.0 / p[l]);
db pr = 1.0 * R / (sl[r - 1] - sl[i - 1] + (R - (sm[r - 1] - sm[i - 1])) * 1.0 / p[r]);
// 如果质量取不到 R, 那就不能用 R 为质量来算密度
if (r > n) pr = 0.0;
ans = max(ans, max(pl, pr));
}
// 算选的都是整块的情况
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
ll m = sm[j] - sm[i - 1];
if (m >= L && m <= R) {
ans = max(ans, 1.0 * m / (sl[j] - sl[i - 1]));
// printf("%lf\n", 1.0 * m / (sl[j] - sl[i - 1]));
}
else if (m >= L && m > R && i == j)
ans = max(ans, p[i] * 1.0);
// printf("[%d, %d], ans:%lf\n", i, j, ans);
}
}
}
int main() {
// freopen("connect2.in", "r", stdin);
scanf("%d%lld%lld", &n, &L, &R);
for (int i = 1; i <= n; ++i) scanf("%d", len + i);
for (int i = 1; i <= n; ++i) scanf("%d", p + i);
// puts("|||||||||||||||||||||||||||||||||||||||||||||");
solve();
reverse(len + 1, len + n + 1);
reverse(p + 1, p + n + 1);
// puts("|||||||||||||||||||||||||||||||||||||||||||||");
solve();
printf("%.10lf\n", ans);
return 0;
}
三、正解(\(O(n \times log(max\left \{p_i \right \})\))
\(O(n^2)\) 的做法在于整块,所以优化这一部分。
机房大佬给出的解法是先二分密度 \(mid\),然后再 \(chk\)。
\(chk\) 函数依旧先枚举左端点,然后二分查找或双指针维护右端点取值范围 \([l,r]\),然后再遍历 \([l, r]\),看看其中是否存在一个 \(j\) 满足 \(\frac{sm_j - sm_{i - 1}}{sl_j - sl_{i - 1}} \geq mid\)。但是这样本质上依旧是 \(O(n^2)\) 暴力。
把判断式子化简一下,成这样:\(mid \times sl_{i - 1} - sm_{i - 1} \geq mid \times sl_j - sm_j\)。发现我们判断的是区间中的最小值是否小于 \(mid \times sl_{i - 1} - sm_{i - 1}\),那就用单调队列维护最小值就好了。
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef double db;
const int maxn = 3e5 + 7;
int n, len[maxn], p[maxn];
ll L, R, sm[maxn], sl[maxn];
db ans;
int q[maxn], h, t;
bool chk(ld x) {
memset(q, 0, sizeof(q));
h = 1, t = 0;
for (int i = 1, r = 0; i <= n; ++i) {
// 单调队列扩展右区间
while (r + 1 <= n && sm[r + 1] - sm[i - 1] <= R) {
++r;
while (h <= t && x * sl[q[t]] - sm[q[t]] > x * sl[r] - sm[r]) --t;
q[++t] = r;
}
// 把不合法的左区间删了
while (h <= t && sm[q[h]] - sm[i - 1] < L) ++h;
if (h > t) continue;
// 满足上述式子
if (x * (ld)sl[i - 1] - (ld)sm[i - 1] >= x * (ld)sl[q[h]] - (ld)sm[q[h]])
return true;
}
return false;
}
void solve() {
for (int i = 1; i <= n; ++i)
sl[i] = sl[i - 1] + len[i],
sm[i] = sm[i - 1] + 1ll * len[i] * p[i];
// 算一端是散块的情况
for (int i = 1, l = 1, r = 0; i <= n; ++i) {
// 单个块质量就超出 R 的话直接特判
if (sm[i] - sm[i - 1] >= R) {
ans = max(ans, p[i] * 1.0);
++l, ++r; continue;
}
while (l <= n && sm[l] - sm[i - 1] < L) ++l;
while (r + 1 <= n && sm[r + 1] - sm[i - 1] <= R) ++r;
if (l > n) break;
db pl = 1.0 * L / (sl[l - 1] - sl[i - 1] + (L - (sm[l - 1] - sm[i - 1])) * 1.0 / p[l]);
db pr = 1.0 * R / (sl[r] - sl[i - 1] + (R - (sm[r] - sm[i - 1])) * 1.0 / p[r + 1]);
if (r > n) pr = 0.0;
ans = max(ans, max(pl, pr));
}
// 算选的都是整块的情况
db l = 0.0, r = 0.0;
for (int i = 1; i <= n; ++i) r = max(r, p[i] * 1.0);
while (r - l >= 1e-7) {
db mid = (l + r) / 2;
if (chk(mid)) l = mid, ans = max(ans, mid);
else r = mid;
}
}
int main() {
// freopen("connect2.in", "r", stdin);
scanf("%d%lld%lld", &n, &L, &R);
for (int i = 1; i <= n; ++i) scanf("%d", len + i);
for (int i = 1; i <= n; ++i) scanf("%d", p + i);
solve();
reverse(len + 1, len + n + 1);
reverse(p + 1, p + n + 1);
solve();
printf("%.10lf\n", ans);
return 0;
}

浙公网安备 33010602011771号