【bzoj1806】[Ioi2007]Miners 矿工配餐 dp

题目描述

有n个物品,每个都是3种之一。现要将这n个物品分成两个序列,对于每个序列中的每个物品,可以得到 它及它前面相邻的两个物品(不足则取全部)中不同种类的个数 的收益。问最大的总收益。

输入

输入的第一行包含一个整数N (1 ≤ N ≤ 100 000), 表示食品车的数目。 第二行包含一个由N个字符组成的字符串,按照配送顺序依次表示食品车配送的食品的类型。每个字符是以下三个大写字母之一:'M' (表示肉类), 'F' (表示鱼类) 或 'B' (表示面包)。

输出

输出一个整数,表示最大的总产煤量。 评分 在45分的测试数据中,食品车的数目至多为20

样例输入

6
MBMFFB

样例输出

12


题解

dp

设$f[i][j][k][l][m]$表示前$i$个物品,第一个序列的最后一个是$j$,第一个序列的倒数第二个是$k$,第二个序列的最后一个是$l$,第二个序列的倒数第二个是$m$的最大总收益(物品不存在则为0)。

那么每次只需要讨论当前物品放到哪个序列即可,状态转移方程详见代码。

由于空间不足,所以需要滚动数组。

时间复杂度$O(n*4^4)$

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[2][4][4][4][4] , v[100010] , cnt[4];
char s[100010];
inline void gmax(int &x , const int y)
{
	x < y ? x = y : 0;
}
inline int calc(int a , int b , int c)
{
	cnt[1] = cnt[2] = cnt[3] = 0 , cnt[a] = 1 , cnt[b] = 1 , cnt[c] = 1;
	return cnt[1] + cnt[2] + cnt[3];
}
int main()
{
	int n , d , i , j , k , l , m , ans = 0;
	scanf("%d%s" , &n , s + 1);
	for(i = 1 ; i <= n ; i ++ ) v[i] = (s[i] == 'M' ? 1 : s[i] == 'F' ? 2 : 3);
	memset(f , 0xc0 , sizeof(f)) , f[0][0][0][0][0] = 0;
	for(d = i = 1 ; i <= n ; i ++ , d ^= 1)
		for(j = 0 ; j <= 3 ; j ++ )
			for(k = 0 ; k <= 3 ; k ++ )
				for(l = 0 ; l <= 3 ; l ++ )
					for(m = 0 ; m <= 3 ; m ++ )
						gmax(f[d][v[i]][j][l][m] , f[d ^ 1][j][k][l][m] + calc(v[i] , j , k)) , gmax(f[d][j][k][v[i]][l] , f[d ^ 1][j][k][l][m] + calc(v[i] , l , m));
	for(i = 0 ; i <= 3 ; i ++ )
		for(j = 0 ; j <= 3 ; j ++ )
			for(k = 0 ; k <= 3 ; k ++ )
				for(l = 0 ; l <= 3 ; l ++ )
					gmax(ans , f[n & 1][i][j][k][l]);
	printf("%d\n" , ans);
	return 0;
}

 

 

posted @ 2017-10-13 10:58  GXZlegend  阅读(282)  评论(0编辑  收藏  举报