状压dp
应用思想,找准状态,多考虑状态和\(f\)答案数组的维数(这个题主要就是找出来状态如何转移)
题目背景
\(BanG Dream!\)里的所有偶像乐队要一起大合唱,不过在排队上出了一些问题。
题目描述
\(N\)个偶像排成一列,他们来自\(M\)个不同的乐队。每个团队至少有一个偶像。
现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起。重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意。
请问最少让多少偶像出列?
输入格式
第一行\(2\)个整数\(N\),\(M\)。
接下来\(N\)个行,每行一个整数\(a_i(1\le a_i \le M)\),表示队列中第i个偶像的团队编号。
输出格式
一个整数,表示答案
输入输出样例
输入
12 4
1
3
2
4
2
1
2
3
1
1
3
4
输出
7
说明/提示
【样例解释】
\(1 \ 3 \ √\\ 3\ 3\\ 2\ 3 \ √\\ 4 \ 4\\ 2 \ 4 \ √\\ 1 \ 2 \ √\\ 2 \ 2\\ 3 \ 2 \ √\\ 1 \ 1\\ 1 \ 1\\ 3 \ 1 \ √\\ 4 \ 1 \ √\)
【数据规模】
对于\(20\%\)的数据,\(N\le 20, M=2\)
对于\(40\%\)的数据,\(N\le 100, M\le 4\)
对于\(70\%\)的数据,\(N\le 2000, M\le 10\)
对于全部数据,\(1\le N\le 10^5\), \(M\le 20\)
分析
看到这友好的乐队数范围,很容易就想到了状压dp,但是状态到底找哪个,记录答案的\(f\)数组开几维都是问题,我们来分析一下,题目中给出的乐队\(M\)的范围是\(20\),而状态压缩就是从小的范围入手的,所以\(f\)数组的状态那一维肯定是关于乐队的,再看题目中问的,询问的是要最少拿出来多少人,那么这个状态肯定就是第几个乐队入队的状态,记录的是当前状态下出队人数的最小,然后枚举最后一个位置的乐队,那么需不需要第二维呢?看起来是不需要的,因为我们每次转移都是从上一次当前乐队的人未放入到放入,然后加上当前乐队人数,减去增加的长度中当前乐队的人数,也就是算出前边需要出队的人数(这个人数用\(sum\)数组记录前缀和来实现),就是这一次需要拿出来的人数,然后每次转移都取一次\(min\),最终状态全为\(1\)的时候的\(f\)数组就是答案。状态转移方程如下:
其中\(num\)是第\(j\)个乐队的人数,\(len\)是到现在状态的队伍长度,预处理一下就可以。\(sum\)就是当前乐队在这一段中的人数。
如果一共有\(M\)个乐队,最终答案就是\(f[(1<
总结一下数组代表的东西:
\(f[i]\)代表状态为\(i\)时出队的最小人数,\(sum[i][j]\)表示前\(i\)长度里,\(j\)乐队的人数,\(num[j]\)代表的就是\(j\)乐队的总人数。
代码
#include
using namespace std;
const int maxn = 21;
int f[1<>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
for(int i=1;i<=n;++i){
num[a[i]]++;//记录每个乐队的总人数
for(int j=1;j<=m;++j)sum[i][j] = sum[i-1][j];//初始化sum数组
sum[i][a[i]]++;//求每个乐队人数的前缀和
}
memset(f,0x3f,sizeof(f));//初始化最大值
f[0] = 0;//一个乐队都没有的时候取0人
for(int i=1;i<(1<