1.19 H.Tokitsukaze and K-Sequence

100 阅读1分钟

题目来自:2023牛客寒假算法基础集训营2

原题链接

题目描述

Tokitsukaze 有一个长度为 nn 的序列 aa,她想把这个序列划分成 kk非空子序列。定义序列的值为这个序列中只出现一次的数字的个数。

对于 k=1nk=1…n,Tokitsukaze 想知道把序列 aa 划分成 kk非空子序列后,所有子序列的值的和最大是多少。

请注意,子序列不一定是连续的。

  1. 所以可以通过统计每一个数对结果的贡献
  2. 数x<=k, 贡献的就是它自己
  3. 数x>k, 贡献的就是(k-1) * (比k大的数的个数)
#include <cstring>
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
//子序列不一定是连续的,所以算每一个数的贡献就行

using namespace std;
typedef long long LL;
const int N = 1e5 + 10;

void solve(){
    int n; cin >> n;
    
    map<int, int> cnt;
    //统计每一个数出现了多少次(v)
    for (int i = 1; i <= n; i ++ ){
        int x; scanf("%d", &x);
        cnt[x] ++;
    }
    
    vector<int> a(n + 1), b(n + 2);
    for (auto [x, v] : cnt){
        //v <= k那么它的贡献就是它本身
        a[v] += v; //统计贡献
        //v > k那么它的贡献就是(k - 1) * (比k大的数的个数)
        b[v] ++; //统计出现v次的个数
    }
    
    //计算前缀和(因为出现次数<=k的都是贡献它自己)
    for (int i = 1; i <= n; i ++ ){
        a[i] = a[i - 1] + a[i];
    }
    
    //计算后缀和(因为出现次数>k的也都参与了贡献)
    for (int i = n; i >= 0; i -- ){
        b[i] = b[i + 1] + b[i];
    }
    
    //结果
    for (int i = 1; i <= n; i ++){
        int ans = a[i] + b[i + 1] * (i - 1); //牛哇
        printf("%d\n", ans);
    }
}
int main(){
    int t; cin >> t;
    while (t -- ){
        solve();
    }
    
    return 0;
}

修改可以更对称

vector<int> a(n + 1), b(n + 1); //!!!!!!!!!!
for (auto [x, v] : cnt){
    //v <= k那么它的贡献就是它本身
    a[v] += v; //统计贡献
    //v > k那么它的贡献就是(k - 1) * (比k大的数的个数)
    //所以当k=i时,只有出现次数为>=i+1的才会算进去
    //当k=n时,就需要b[n+1]的后缀和(边界问题)
    //所以我们可以在计数阶段将v-1,那么。。。
    b[v - 1] ++; //统计出现v次的个数 !!!!!!!!
}

//计算前缀和(因为出现次数<=k的都是贡献它自己)
for (int i = 1; i <= n; i ++ ){
    a[i] += a[i - 1];
}

//计算后缀和(因为出现次数>k的也都参与了贡献)
for (int i = n; i >= 0; i -- ){
    b[i] += b[i + 1];
}

//结果
for (int i = 1; i <= n; i ++){
    //这里b[i]其实就是v-1=i -> v=i+1;代换,但是避免了边界问题
    int ans = a[i] + b[i] * (i - 1); //牛哇 !!!!!!!!!
    printf("%d\n", ans);
}