CF999F 纸牌与欢乐
1 CF999F 纸牌与欢乐
2 题目描述
有 \(𝑛\) 个玩家坐在牌桌旁。每个玩家都有一个最喜欢的号码。\(𝑗\) 号选手最喜欢的号码是 \(𝑓_𝑗\)。
桌子上有 \(𝑘⋅𝑛\) 张纸牌。每张纸牌包含一个整数:第 \(𝑖\) 张纸牌包含数字 \(𝑐_𝑖\)。另外,给你提供一个序列\(ℎ_1,ℎ_2,...,ℎ_𝑘\)。它的含义如下。
玩家必须合理的方式分发纸牌,使得每个人都正好拿到 \(𝑘\) 张卡片。在所有的纸牌分发之后,每个玩家都计算自己最喜欢的数字纸牌数量。如果玩家持有包含他最喜欢的数字的 \(𝑡\) 张纸牌,玩家的欢乐数字是 \(ℎ_𝑡\)。如果一个玩家没有得到他最喜欢的数字(即 \(𝑡=0\))的纸牌,那么他的欢乐水平是 \(0\)。
在分发完纸牌后,输出玩家最大可能的总快乐水平。请注意,序列 \(ℎ1,...,ℎ𝑘\) 对所有玩家都是相同的。
3 题解
容易看出,这道题需要 \(dp\) 来计算:不同的 \(h\) 值会导致贪心算出的不一定是最优解。
我们分析题目,发现 \(f\) 数组的顺序与答案无关,因此我们可以把 \(f\) 数组先进行排序。然后我们发现第二个与答案无关的值:没有人喜欢的数。由于给出的数数量一直为 \(n*k\),所以最后剩下这些数把每个人的份量全部补齐即可。题目就换了个意思出现在了我们眼前:给出一串数和一些喜欢这些数中某个数的人,每个人最多能拿到 \(k\) 个数,每个人拿到 \(x\) 个喜欢的数会产生 \(h_x\) 的价值,问价值最大值是多少。由于每个人只喜欢某一个数,我们可以将 \(f\) 数组去重并记录:有多少人喜欢哪个数。并且在 \(c\) 数组也进行类似操作,计算某个数出现的次数。这样,假设一共有 \(y\) 种不同的被喜欢的数的值,问题就进一步转化为:进行 \(y\) 次询问,每次给出一个数的数量与喜欢这个数的人,每个人最大可以拿 \(k\) 个数,每个人拿到 \(x\) 个喜欢的数会产生 \(h_x\) 的价值,求最大价值。
如此,我们就可以想到 \(dp\) 的状态为:\(dp_{i,j}\) 表示前 \(i\) 个人拿了前 \(j\) 个该数。方程为:\(dp_{i, j} = max\{dp_{i-1, j - l} + h_l\}(l \in [0, min(j, k)])\)。转移方程的大概意思为:当前这个人可以选择不拿数,也可以选择拿 \(1 - min(j,k)\) 个数,最终算出产生的最大价值。
4 代码(空格警告):
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505;
int n, k, cnt, ans;
int c[N*10], f[N], h[10];
int num[N], peo[N];
int dp[N][N*10];
int main()
{
cin >> n >> k;
for (int i = 1; i <= n*k; i++) cin >> c[i];
for (int i = 1; i <= n; i++) cin >> f[i];
for (int i = 1; i <= k; i++) cin >> h[i];
sort(f+1, f+n+1);
for (int i = 1; i <= n; i++)
{
if (f[i] != f[i-1])
{
cnt++;
for (int j = 1; j <= n*k; j++)
{
if (f[i] == c[j]) num[cnt]++;
}
}
peo[cnt]++;
}
for (int i = 1; i <= cnt; i++)
{
memset(dp, 0, sizeof(dp));
for (int j = 1; j <= peo[i]; j++)
{
for (int kk = 1; kk <= num[i]; kk++)
{
for (int l = 0; l <= min(k, kk); l++)
{
dp[j][kk] = max(dp[j][kk], dp[j-1][kk-l] + h[l]);
}
}
}
ans += dp[peo[i]][num[i]];
}
cout << ans;
return 0;
}