P2072 宗教问题

僤鯓⒐⒋嵵緔 提交于 2019-12-03 05:30:06

题目背景

在一个地区有许多种宗教,不同信仰的教徒经常发生矛盾,最为治安管理的人需要把这些人分开,以免矛盾激化。

题目描述

已知一个地方有M种宗教(编号为1—M),有N个教徒(编号为1—N),每个教徒信且只信一种宗教。现在要按顺序把这N个教徒分成一些集体,每个集体的危险值定义为这个集体中的宗教种数,且一个集体的宗教种类不能超过K种,否则就会无限危险,

问: 1.这N个教徒至少要分为几个集体,

2.这些集体的危险值总和至少为多少。

输入格式

第一行三个正整数N M K,以空格隔开

第二行N个正整数,为每个教徒信的宗教编号

输出格式

第一行 一个正整数,为最少集体数

第二行 一个正整数,为最小危险值。

输入输出样例

输入 #1
10 4 3

1 2 3 4 3 4 3 2 1 2

输出 #1
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:

  1. 先读入题目中要求读入的n,m,k和每个教徒信的宗教编号。

  2. 开始写核心部分。

    ①定义两个数组为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);

    自己可以慢慢体会含义,看完上面的解释应该能懂

  3. 最后我们就可以输出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;
}

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!