洛谷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;
}
posted @ 2020-02-02 15:30  quanjun  阅读(117)  评论(0编辑  收藏  举报