洛谷P2072 宗教问题 题解 动态规划
题目链接:https://www.luogu.com.cn/problem/P2072
题目大意:
已知一个地方有M种宗教(编号为1—M),有N个教徒(编号为1—N),每个教徒信且只信一种宗教。现在要按顺序把这N个教徒分成一些集体,每个集体的危险值定义为这个集体中的宗教种数,且一个集体的宗教种类不能超过K种,否则就会无限危险,
问:
1.这N个教徒至少要分为几个集体,
2.这些集体的危险值总和至少为多少。
解题思路:
动态规划。
定义 \(f[i][k]\) 表示前 \(i\) 个教徒分成了 \(k\) 个集体的危险值总和的最小值。
则:
\[f[i][k] = \min_{j = j1 \rightarrow i-1} (f[j-1][k-1] + C_{j,i})
\]
其中,这里的 \(j1\) 表示的是满足区间 \([j+1, i]\) 范围构成的集体的危险值 \(\le k\) 的最大的 \(j\)。
然后我们在计算 \(f[i][k]\) 的时候,不要从 \(0 \rightarrow i-1\) 去枚举 \(j\) ,而应该从 \(i-1 \rightarrow 0\) 去枚举 \(j\) ,这样能够节省 \(O(n)\) 的时间(这部分时间用于计算区间 \([j,i]\) 内的危险值)。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, m, k, a[maxn], f[maxn][maxn];
bool vis[22];
void test() {
puts("[test]");
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= i; j ++) {
printf("%4d", f[i][j]);
}
puts("");
}
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
memset(f, -1, sizeof(f));
f[0][0] = 0;
for (int i = 1; i <= n; i ++) {
memset(vis, 0, sizeof(vis));
int cnt = 1;
vis[a[i]] = true;
for (int j = i-1; j >= 0; j --) {
for (int k = 1; k <= i; k ++) {
if (f[j][k-1] != -1) {
if (f[i][k] == -1 || f[i][k] > f[j][k-1] + cnt)
f[i][k] = f[j][k-1] + cnt;
}
}
if (!vis[a[j]]) {
vis[a[j]] = true;
cnt ++;
if (cnt > k) break;
}
}
}
int cnt = -1, sum = -1;
for (int i = 1; i <= n; i ++) {
if (f[n][i] != -1) {
if (cnt == -1) cnt = i;
if (sum == -1 || sum > f[n][i])
sum = f[n][i];
}
}
cout << cnt << "\n" << sum << endl;
// test();
return 0;
}