Loading

Kevin and Competition Memories

思路

题意还是比较清楚的

考虑当 \(k\) 确定时, 怎么计算 \(\rm{Kevin}\) 的最优排名之和

手动模拟一下样例, 这个题应该要以人为基准, 然后再看题目难度对于 \(\rm{rank}\) 的影响

首先我们思考一下, 对于一个能力值序列 \(a\) , 插入题目怎样影响了 \(\rm{Kevin}\) 的排名

对于一个题目, 他的难度可以使得 \(k\) 个人做出来这个题并且 \(\rm{Kevin}\) 没法做出来, 那么他对 \(\rm{Kevin}\) 排名的影响就是 \(k\) , 这是显然的, 如果 \(\rm{Kevin}\) 做出来了那么就没有影响

同时, 你发现对于多道题, 他们对 \(\rm{Kevin}\) 排名的影响仅仅是单独每道题的影响取 \(\max\)

那么策略就很明显了, 我们对于每个题目的影响, 一定是让小的与小的组合, 大的与大的组合, 处理影响是 \(\mathcal{O} (n \log n)\) 的, 然后对于排序后的题目, 选择即可

时间复杂度 \(\mathcal{O} (n \log n + \frac{m}{1} + \frac{m}{2} + \cdots + \frac{m}{m}) = \mathcal{O} (n \log n + m \log m)\)


考虑复习

  • 定义操作 (约束) 和开销 / 收益, 要求最值化开销 / 收益
    • 推式子计算约束条件
    • 模拟操作情况, 找到最好开销, 注意最大和最小, 一般来说可以贪贪心 (将简单情况先处理, 然后在基础上处理最值)
    • 考虑操作对答案的影响 (推式子) , 据此对操作进行处理
      • 推导每个元素对答案的贡献
      • 推导动态规划
    • 判断是否存在操作满足约束
      • 模拟, 对于重复情况去重
    • 要求一个数列的多个部分 \((\)前缀, 子串 \(\cdots\)\()\) 的成本
      • 贪心找到最优操作的构造方法, 加上优化 / 找共同点
题意

nn 个人, 每个人有数值 wiw_i , mm 道题目, 每道题目有数值 viv_i
如果一个人的数值 \geq 题目数值, 那么他可以做这道题目
一个人的排名定义为那些比他解掉更多题目的参赛者数量加一

对于 k[1,n]k \in [1, n]
要求你求出:
如果一场比赛有 kk 道题, 准许你放弃 m mod km \textrm{ mod } k 道题, 那么在 mk\left\lfloor \frac{m}{k} \right\rfloor 场比赛中, 第一个人最小的排名之和为多少

先考虑对于一个确定的 \(k\) , 如何处理最优解\((\)找到最好开销\()\)
首先把所有第 \(1\) 个人做得出来的题和数值低于第 \(1\) 个人的人甩掉, 没有影响
剩下的题, 计算出有多少个人做得出来, 记为 \(p_i\)

考虑单个元素对答案的影响, 也即一个题目会对排名有多少影响
对于一场比赛, 假设其题目集合为 \(\mathbb{S} = \{p_1, p_2, \dots, p_k\}\) , 那么排名应该会是 \(\max\limits_{i=1}^{k} p_i\)
为什么?
对于题目权值 \(p_i\) , 其操作就是对于 \(1 \sim p_i\) 的人, 做题数加 \(1\), 那么显然只有最大的一次是贡献

知道了这个, 我们考虑贪心放置题目
从小到大放置分组即可

实现

框架

先双指针处理题目代价, 然后再随便处理即可

代码

#include <bits/stdc++.h>
#define int long long
const int MAXN = 3e5 + 20;

int n, m; 
int Abi[MAXN], Mar[MAXN];
int Kmar, breakpoint;
int Cost[MAXN];
/*预处理所有问题的代价*/
void init() {
    for (int i = 1; i <= m; i++) Cost[i] = 0;
    int cnt = 0;
    for (int i = 1, pointer = breakpoint; i <= m && pointer <= n; i++) {
        if (Mar[i] <= Kmar) continue;
        while (Abi[pointer] < Mar[i] && pointer <= n) pointer++, cnt++;
        Cost[i] = n - cnt - breakpoint + 1;
    }
    std::sort(Cost + 1, Cost + m + 1);
}

signed main()
{
    int T; scanf("%lld", &T);
    while (T--) {
        scanf("%lld %lld", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%lld", &Abi[i]);
        for (int i = 1; i <= m; i++) scanf("%lld", &Mar[i]);

        Kmar = Abi[1];
        std::sort(Abi + 1, Abi + n + 1), std::sort(Mar + 1, Mar + m + 1);
        for (int i = 1; i <= n; i++)
            if (Abi[i] > Kmar) { breakpoint = i; break; }
        
        init();

        for (int k = 1; k <= m; k++) {
            int Ans = 0;
            for (int i = k; i <= m; i += k) {
                Ans += 1 + Cost[i];
            }
            printf("%lld ", Ans);
        }
        printf("\n");
    }

    return 0;
}

总结

善于从样例来找到性质

一般来说, 先去除无效数据是有用的

posted @ 2024-12-20 16:25  Yorg  阅读(51)  评论(0)    收藏  举报