P3694 邦邦的大合唱站队

Description

N个偶像排成一列,他们来自M个不同的乐队。每个团队至少有一个偶像。

现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起。重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意。

请问最少让多少偶像出列?

Solution

发现 \(n\) 的数据范围较大,但 \(m\) 的范围较小,可以考虑用 \(m\) 状压DP。

\(f[i]\) 表示考虑 \(i\) 状态下,为 \(1\) 的那些位置的乐队已经归好队的最小出队人数,并且把那些乐队尽量放在前端,此时乐队的人数也确定下来了。

思考转移,考虑现在要把第 \(j\) 个的队伍归好队,并把他们放在已归好队的队伍最后面,设第 \(j\) 个队伍共
\(total[j]\) 个人,在区间 \((x,y)\) 段(归好队的末尾段)共有 \(k\) 个人,则转移为

\(f[i] = min{f[i - 2^{j}] + total[j] - sum[x,y][k]}\)

至于 \([x,y]\) 段共有多少个第 \(j\) 个乐队的人,可以在输入时前缀和预处理出来。

最终答案即为 \(f[2^{m}-1]\)

Code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <stack>
#define rep(_, __, ___) for(int _ = __; _ <= ___; _++)
#define per(_, __, ___) for(int _ = __; _ >= ___; _--)
using namespace std;
const int N = 100005;
const int M = 20;
int n, m;
int a[N], total[M + 5], sum[N][M];
int f[1 << M];
int main()
{
	scanf("%d%d", &n, &m);
	rep(i, 1, n)
	{
		scanf("%d", &a[i]);
		rep(j, 1, m)
		{
			sum[i][j] = sum[i - 1][j];
		}
		total[a[i]]++;
		sum[i][a[i]]++;//预处理每个乐团的总人数与前缀和
	}
	memset(f, 0x3f, sizeof f);//注意dp数组初始化
	f[0] = 0;
	for(int i = 1; i < (1 << m); i++)
	{
		int oklen = 0;
		rep(j, 1, m)
		{
			if((i >> (j - 1)) & 1)
			{
				oklen += total[j];
			}
		}
		rep(j, 1, m)
		{
			if((i >> (j - 1)) & 1) //从第 j 个乐队未归好队转移
			{	
				f[i] = min(f[i], f[i - (1 << (j - 1))] + total[j] - (sum[oklen][j] - sum[oklen - total[j] + 1 - 1][j]));	//计算把第 j 个乐队放最末尾的转移	
			} 
		}
	}
	printf("%d", f[(1 << m) - 1]);
	return 0;
}
posted @ 2022-07-17 19:13  panjx  阅读(30)  评论(0编辑  收藏  举报