CF1077E Thematic Contests 题解

Thematic Contests

题意

\(n\) 个问题,每个问题有一个分类 \(a_i\)

现在要举办一些比赛,要求:

  • 一场比赛中所有题目的分类相同。
  • 所有比赛的分类是互不相同的。
  • 第一场比赛的题目数量任意,但从第二场开始,每一场比赛的题目数量都必须是前一场的两倍。

求所有比赛的题目数量之和的最大值。

思路

一看数据范围,就知道要离散化。

有一个明显的贪心:按照每个分类的出现次数排序。把出现次数较少的分类放在后面是不可能使答案更优的,所以可以排序。

(温馨提示:接下来的部分中第 \(i\) 种主题指的都是离散化后的。)

我的做法是 dp

  • 状态:\(dp_{i,j}\),表示第 \(i\) 种主题选择 \(j\) 道题情况下,前 \(i\) 种主题选择的题目数量最大值。
  • 转移:\(dp_{i,j}=\max\limits_{k=1}^{i-1}\{\max\limits_{l=1,l\times 2=j}^{b_k}\{dp_{k,l} \}\}+j\)
  • 目标状态:\(\max\limits_{i=1}^{m}\{\max\limits_{j=1}^{b_i}\{dp_{i,j} \}\}\),其中 \(m\) 表示离散化后主题的数量,\(b_i\) 表示第 \(i\) 个主题中的题目数量。

但是很明显,二维状态存不下、转移时直接查找会 TLE,怎么办呢?

我们可以把最后一次比赛选择 \(i\) 道题的最大答案记录下来,这样既可以省掉 \(i\) 这一个维度,还可以节省掉查找的时间,两全其美。

具体实现看代码。

Code

点击查看代码
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2e5 + 10;

int n, a[N], m, b[N], c[N], dp[N], ans;
// c[i] 用来记录最后一次比赛选择 i 道题的最大答案

int main(){
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  // 以下内容为离散化
  sort(a + 1, a + n + 1);
  for (int i = 1; i <= n; i++) {
    if (a[i] == a[i - 1]) {
      b[m]++;
    } else {
      b[++m] = 1;
    }
  }
  // 接下来是 dp 求答案
  sort(b + 1, b + m + 1); // 按出现次数从小到大排序
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= b[i]; j++) { // 离散化后第 i 个主题选择了 j 个题目
      dp[j] = 0;
      if (!(j % 2)) { // j 是偶数,那么它的前面还可以添加比赛
        dp[j] = c[j / 2];
      }
      dp[j] += j; // 加上这次选择的题目数量
      ans = max(ans, dp[j]); // 统计答案
    }
    for (int j = 1; j <= b[i]; j++) {
      c[j] = max(c[j], dp[j]); // 注意细节!这里的最大值是不可以放在上面去更新的!
    }
  }
  cout << ans;
  return 0;
}
posted @ 2023-05-17 21:37  wnsyou  阅读(16)  评论(0编辑  收藏  举报