题目 | 维克多词典
第九届中国大学生程序设计竞赛(秦皇岛)-(CCPC2023-Qinhuangdao)
J - 维克多词典
https://cpc.csgrandeur.cn/csgoj/problemset/problem?pid=1233
1 second / 256 megabytes
standard input / standard output
题目
Keyi 是一个热爱读书的中学生,并养成了每天早上阅读的习惯。
随着高考的临近,Keyi 计划每天从《维克多词典》中学习几个单词。
然而,她记忆单词的方法有点奇特。如果她今天学习了一个长度为 $k$ 的单词,她坚持要在当天记住词典中所有长度为 $k$ 的单词。
但是,Keyi 每天的精力是有限的,她一天最多只能学习 $W$ 个单词,否则她就记不住它们了。
Keyi 不确定要如何有效地安排学习计划,以便尽量用最少的天数完成《维克多词典》的记忆。因此,她恳请您的帮助。
输入
第一行包含两个整数 $n$ 和 $W\ (1\le W \le n \le 50000)$,表示《维克多词典》内单词的数量以及 Keyi 每天最多学习的单词数量。
第二行包含 $n$ 个整数 $a_i\ (1 \le a_i \le 13)$,$a_i$ 表示第 $i\ (1 \le i \le n)$ 个单词的长度。
保证对于相同长度的单词,它们不会出现超过 $W$ 次。
输出
输出一行表示 Keyi 记住整本《维克多词典》所需的最少天数。
样例
输入
5 4
1 2 1 2 1
输出
2
题解
词长度范围仅 $13$,很小,可以考虑状态压缩 DP. 使用 $13$ 位的二进制数表示状态,第 $i$ 位为 $1$ 代表该词已经被学习,反之没有,遍历 $2^{13}$ 个状态进行转移即可。
对于一个状态 $i$,它可以通过学习它的非空子集 $j$ 转移得到,学习前的状态为 $i\oplus j$.
例如对于 $i=0000000001011_2$,它可以通过七种学习方式 $0000000001011_2$,$0000000000011_2$,$0000000001001_2$,$0000000001010_2$,$0000000000001_2$,$0000000000010_2$,$0000000001000_2$ 转移得到。
遍历一个状态的非空子集的方式可以写作:for (int j = i; j; j = (j - 1) & i)
但是,如果一次学习的量超过 $w$,那么也是不成立的,因此每次需要判断学习的量有没有超标。可以预处理出来每种学习方式的数量。
代码
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
constexpr int MAXN = 13;
int cnt[MAXN];
int n, w;
int sum[1 << MAXN], dp[1 << MAXN];
void solve()
{
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
cin >> n >> w;
for (int i = 0; i < n; i++)
{
int t;
cin >> t;
cnt[t - 1]++;
}
for (int i = 0; i < (1 << MAXN); i++)
for (int j = 0; j < MAXN; j++)
if (i >> j & 1)
sum[i] += cnt[j];
for (int i = 0; i < (1 << MAXN); i++)
for (int j = i; j; j = (j - 1) & i)
if (sum[j] <= w)
dp[i] = min(dp[i], dp[i ^ j] + 1);
cout << dp[(1 << MAXN) - 1] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi