题目背景
在一个地区有许多种宗教,不同信仰的教徒经常发生矛盾,最为治安管理的人需要把这些人分开,以免矛盾激化。
题目描述
已知一个地方有M种宗教(编号为1—M),有N个教徒(编号为1—N),每个教徒信且只信一种宗教。现在要按顺序把这N个教徒分成一些集体,每个集体的危险值定义为这个集体中的宗教种数,且一个集体的宗教种类不能超过K种,否则就会无限危险,
问: 1.这N个教徒至少要分为几个集体,
2.这些集体的危险值总和至少为多少。
输入格式
第一行三个正整数N M K,以空格隔开
第二行N个正整数,为每个教徒信的宗教编号
输出格式
第一行 一个正整数,为最少集体数
第二行 一个正整数,为最小危险值。
输入输出样例
10 4 3 1 2 3 4 3 4 3 2 1 2
3 6
说明/提示
【样例解释】
最少集体数:1 2 3 // 4 3 4 3 2 // 1 2 共3个集体
最小危险值:1 2 // 3 4 3 4 3 // 2 1 2 2+2+2=6
【数据范围】
对于20%的数据 N≤20
对于50%的数据 N≤100
对于100%的数据 N≤1000 M≤20 1≤K<M
思路
线性DP*2:
-
先读入题目中要求读入的n,m,k和每个教徒信的宗教编号。
-
开始写核心部分。
①定义两个数组为f和dp,f[i]表示前i个教徒至少要分为几个集体,dp[i]表示前i个集体的危险值总和至少为多少。
②确定边界,这个比较好写,f[1]=a[1](注:a[1]是第1个教徒信的宗教编号);dp[1]=1。
③确定循环。共两层循环。第一层循环是从1到n,遍历n个教徒,用i作循环变量;第二层循环是从i到1逆序循环,循环变量j枚举的是前i个教徒的最后一个集体中的第一个教徒序号(也可以说是确定最后集体的大小)。
④判断前i个教徒的最后一个集体是否满足题目中条件。这并不难,首先定义一个桶,再定义统计最后集体里不同宗教编号数量为n1。发现如果第j个教徒所信仰的宗教编号没有出现过,那么就标记第j个教徒所信仰的宗教编号出现过,并且n1++。当发现n1大于k时,就直接break(想想为什么可以这样做)。
⑤现在就开始推状态转移方程了。其实方程特别好写,如下
f[i]=min(f[i],f[j-1]+1); dp[i]=min(dp[i],dp[j-1]+n1);
自己可以慢慢体会含义,看完上面的解释应该能懂
-
最后我们就可以输出f[n]和dp[n]了。
时间复杂度为O(n^2)
源自: LLR_TEST
代码
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N=1010; const int oo=1e9; int n,m,k; int a[N],f[N]; int dp[N],flag[N]; int main () { scanf("%d%d%d",&n,&m,&k); for(int i=1; i<=n; i++) scanf("%d",&a[i]); f[1]=1; dp[1]=1; for(int i=2; i<=n; i++) { dp[i]=f[i]=oo; memset(flag,0,sizeof(flag)); int cnt=0; for(int j=i; j>=1; j--) { if(!flag[a[j]]) { cnt++; flag[a[j]]=true; } if(cnt>k) break; f[i]=min(f[i],f[j-1]+1); dp[i]=min(dp[i],dp[j-1]+cnt); } } printf("%d\n%d\n",f[n],dp[n]); return 0; }