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