2021 寒假基础第二场 G https://ac.nowcoder.com/acm/contest/9982/G
12 Feb 更新:修正了一处错误,更新了代码。
我们把问题拆成几个,逐个解决:
怎么统计每支队过了多少题?如何统计过了 X 题的有多少支队伍?
这里假设你已经知道差分是啥了,可能第一时间会想到一个 1e9 的数组 diff 作为差分数组,
也就是说,我们使用 diff[i] 表示第 支队伍的过题数和第
支队伍的过题数相差多少。
那么对于每道题给定的 ,使第
到
支队伍的过题数加一,相邻队伍过题数的变化情况无非就两个:
- 第
支队伍相比第
支队伍多过了
题,也就是
diff[i]++。 - 第
支队伍相比第
支队伍少过了
题,也就是
diff[r+1]--。
接下来我们要求出每个队伍过多少题,只需要前缀和一次就行。
毕竟我们知道没有第 支队伍,所以
diff[1] 其实就是第一支队伍的过题数,首项知道前后项关系也知道自然任意项都能推出来。
但是 1e9 这个数字是在太大了,事实上我们并不需要真的具体到每一支队伍的过题数都需要知道,据个例子:
现在有 20 支队,就两道题,现在告诉你第 支队过了
题,第
支队过了
题。
我们也就关心第 队的情况,因为这些都是边界,他们的过题数与前一支队伍的过题数不同。
具体而言,对于给定的 ,我们只需要记录第
和第
支队伍的过题与前支队伍有和差异就行。
对于其他队伍,我们知道他们跟前一支队伍过题数相同那就足够了,所以我们这里使用的是 map。
map<int, int> diff; // 记录 "关键队伍" 的过题差异信息
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int l, r;
cin >> l >> r;
// [l,r] 区间加一,用差分处理
diff[l] = diff[l] + 1; // 第 l 支队伍相比第 l-1 支队伍多过了 1 题
diff[r + 1] = diff[r + 1] - 1; // 第 r+1 支队伍相比第 r 支队伍少过了 1 题
} 现在 题最少
支最多
支 "关键队伍" 的过题差异信息我都已经全部记录下来了,假设我在全部 "关键队伍" 中记录了
diff[a] 和 diff[b],,然而从第
到
支队伍我一支都没记录(
和
两支队伍不一定是在同一题被记录的),我们可以得到的信息有:
- 从第
支队伍到第
支队伍,一共
支队伍过题数都是一样的。
- 基于第 1 点,用第
支队伍的过题数加上
diff[b]就是第支队伍的过题数了。
所以即使改用了 map,我们还是用前缀和计算所有关键队伍的实际过题数量。
同时还是基于第 1 点,我们也可以统计统计过了 X 题的有多少支队伍了,由于题目数相对比较少,直接使用数组记录即可。
最后一支 "关键队伍" 以及它后面的队伍是不会纳入
cnt统计的,也不用专门处理,事实上这也不会影响到答案的:因为这最后一支 "关键队伍" 要不就是不存在的第支队伍,要不这支队以及它后面的队伍都爆零了(那是肯定没有奖的)。另外因为
map的特性,遍历map肯定是按队伍编号从小到大遍历的,所以放心好了~
int cnt[100010]; // cnt[i] 表示过题数为 i 的队伍数
int lastid = 1; // 记录上一个 "关键队伍" 的编号
int last = 0; // 记录上一个 "关键队伍" 的过题数量
for (auto &i : diff) // 遍历 map
{
// i.first 就是遍历到的 "关键队伍" 的编号
// i.second 就是遍历到的 "关键队伍" 的过题数量
i.second = i.second + last; // 前缀和
cnt[last] += i.first - lastid; // 从第 lastid 到 i.first-1 支队伍的过题数量都是一样的
last = i.second;
lastid = i.first;
} 之前的算法入门课练习赛其实有一道很相似的题目,也是 map + 差分,可以去围观一下:
https://ac.nowcoder.com/acm/contest/5773/D
如何知道第 Y 名过了多少题?
我们前面使用了 cnt[i] 记录过题数为 的队伍数,其实这些就是边界情况,可以很好地利用。
我们假设现在一共就两题,那么:
- 从第
1名到第cnt[2]名都过了两题; - 从第
cnt[2]+1名到第cnt[2]+cnt[1]名都过了一题; - 从第
cnt[2]+cnt[1]+1名到第cnt[2]+cnt[1]+cnt[0]名都爆零了。
那么我们还是使用前缀和,从高过题量到低过题量一路下去,当我们发现 Y 刚好就卡在某个范围内,那么过题数就是它了。
int s = 0;
int l[] = {(int)ceil(1.0 * n / 10), (int)ceil(1.0 * n / 4), (int)ceil(1.0 * n / 2)}; // 现在要求出这些排名过的题目数
int t[] = {1, 1, 1}; // 求出来后就放到这个数组
for (int i = m; i >= 1; i--) // 从高过题量到低过题量,获奖过题数最低是 1
{
for (int j = 0; j < 3; j++)
{
// 如果 cnt[i] 为 0,那么下面的条件肯定不成立,不用专门考虑
if (s + 1 <= l[j] && l[j] <= s + cnt[i]) // 把两个边界卡出来,看看 l[j] 在不在这个范围里面
{
t[j] = i;
}
}
s += cnt[i]; // 前缀和
} 如何统计金银铜队伍数量?
现在我们已经知道金银铜牌至少需要多少题,那么对于每一个过题数量,我们看看它是否满足金牌的要求,不满足再看看是否满足银牌的要求,不满足再看看是否满足铜牌的要求就行了。输出答案,完结撒花!
int r[] = {0, 0, 0}; // 记录金银铜的队伍数量,也就是答案
for (int i = m; i >= 0; i--)
{
for (int j = 0; j < 3; j++) // 按照金银铜的顺序逐个试
{
if (i >= t[j])
{
r[j] += cnt[i];
break; // 满足高等级奖牌的要求就不用往下试了
}
}
}
cout << r[0] << ' ' << r[1] << ' ' << r[2] << endl; 代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=46768944

京公网安备 11010502036488号