【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; }