题意
T T T组测试. n n n个学生参加考试. 如果第 i i i个学生发挥得好, 他可以得 a i a_i ai分; 否则他只能得 b i b_i bi分. 一场考试结束后, 设最高分为 x x x, 及格线为 x ⋅ p x \cdot p x⋅p%. 问可能最多有几个学生及格.
1 ≤ T ≤ 5 ⋅ 1 0 3 , 1 ≤ n ≤ 2 ⋅ 1 0 5 , 1 ≤ p ≤ 100 , 1 ≤ b i ≤ a i ≤ 1 0 9 1 \le T \le 5 \cdot 10^3, 1 \le n \le 2 \cdot 10^5, 1 \le p \le 100, 1 \le b_i \le a_i \le 10^9 1≤T≤5⋅103,1≤n≤2⋅105,1≤p≤100,1≤bi≤ai≤109
题解
枚举最高分的时候, 应该对分数进行从大到小的顺序排序, 然后依次考虑, 考虑过的分数是不能选的.
那么既然要枚举最高分, 那么一定得把 n n n个学生"拆"开, 一共 2 n 2n 2n个分数. 这样才可以枚举最高分.
每个学生都至少需要一个分数, 所以一但在枚举的过程中发现同一个学生的两个分数都枚举过了, 那么直接结束, 即枚举完第一个出现的 b b b结束.
我们需要维护
[
i
,
p
o
s
]
[i, pos]
[i,pos]中学生的个数. 注意到
x
x
x和
x
⋅
p
x \cdot p
x⋅p% 都是单调的, 那么只需要维护一个滑动窗口即可. 具体来说: 维护一个数组 vis[]
, 表示学生在窗口内出现的次数. 和 tot
, 表示窗口内学生总数.
- 尾指针拓展一个分数时, 找到对应的学生, 记录次数(
vis[tail]++
). 如果是第一次找到这学生( 修改前的vis[tail] == 0
), 则窗口内学生总数加一(tot++
). 不断拓展尾指针, 直到他不在及格线内(score[tail] < score[head] * p / 100
). - 头指针移除时, 找到对应学生, 减去次数(
vis[head]--
), 同理维护tot
.
复杂度 O ( n ) O(n) O(n)
最后注意score[head] * p / 100
,
a
(
b
)
a(b)
a(b) 最大可为
1
0
9
10^9
109,
p
p
p最大为
100
100
100, 直接乘就炸int啦!!!
我才不会告诉你因为这个细节vp时调了3个小时换了2种做法WA了9发, 而且当场还没一个人找到这个极其**的失误
{{% code %}}
const int maxn = 2e5+10;
int t, n, p;
int a[maxn], b[maxn], vis[maxn];
struct Score {
int num, flag, id;
bool operator < (const Score &N) const {
return num == N.num ? flag < N.flag : num > N.num;
}
} score[maxn * 2];
int main() {
scanf("%d", &t);
for (int kase = 1; kase <= t; kase++) {
scanf("%d%d", &n, &p);
int ans = 0;
for (int i = 1; i <= n; i++) {
scanf("%d%d", a + i, b + i);
score[i] = Score{a[i], 0, i};
score[i + n] = Score{b[i], 1, i};
vis[i] = 0;
}
sort(score + 1, score + 1 + 2 * n);
int tot = 0, tail = 1;
for (int head = 1; head <= 2 * n; head++) {
while (score[tail].num >= score[head].num * (p / 100.0)) {
int id = score[tail].id;
if (vis[id] == 0)
tot++;
vis[id]++;
tail++;
}
ans = max(ans, tot);
if (score[head].flag)
break;
int id = score[head].id;
if (vis[id] == 1)
tot--;
vis[id]--;
}
printf("Case #%d: %d\n", kase, ans);
}
return 0;
}
{{% /code %}}