题目来自:2023牛客寒假算法基础集训营2
题目描述
Tokitsukaze 有一个长度为 的序列 ,她想把这个序列划分成 个非空子序列。定义序列的值为这个序列中只出现一次的数字的个数。
对于 ,Tokitsukaze 想知道把序列 划分成 个非空子序列后,所有子序列的值的和最大是多少。
请注意,子序列不一定是连续的。
- 所以可以通过统计每一个数对结果的贡献
- 数x<=k, 贡献的就是它自己
- 数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);
}