*P3694 邦邦的大合唱站队[dp]

题目描述

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

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

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

解析

有点难。

定义二进制状态\(i\)表示自右往左第\(j\)位二进制数为第\(j\)个团队排队状态,其中1表示排好,0反之。

我们不妨大胆假设对于状态\(i\),这些排好的团队就都站在最前面,那么没排好的团队就只能站在她们后面,我们遍历所有没排好队的团队,接在排好队的后面。仔细考察,会发现如此定义也可以遍历整个状态空间,是可行的。

\(dp[i]\)表示状态\(i\)时,假设排好队的所有团队都站在最前面出列的最少人数。对于这个状态\(i\),它可以从所有满足一个条件的它的子集转移而来,即其子集中某个团队未排好队的状态。

首先,对于一个状态\(i\),总人数不变,那么对于排在最后的一个团队的位置我们也就知道了。

对于一个转移,要让没排好队的那一个团队的人排好队,它会造成所有不属于这个团队的人出队。

预处理出每个团队的人的前缀和进行一个小小的优化即可轻松A掉这道题。

参考代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstdlib>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define N 100010
#define MOD 2520
#define E 1e-12
using namespace std;
inline int read()
{
	int f=1,x=0;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int sum[21][N],n,m,dp[N*20];
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i){
		int x=read();
		for(int j=1;j<=m;++j) sum[j][i]=sum[j][i-1];
		sum[x][i]++;
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=0;i<=(1<<m)-1;++i){
		int tmp=0;
		for(int j=1;j<=m;++j)
			if((i>>(j-1))&1) tmp+=sum[j][n];//该团队已经排好
		for(int j=1;j<=m;++j){
			if((i>>(j-1))&1) continue;
			int l=tmp,r=tmp+sum[j][n];//没排好的这个团队要排到的位置
			dp[i|1<<(j-1)]=min(dp[i|1<<(j-1)],dp[i]+r-l-sum[j][r]+sum[j][l]);
		}
	}
	printf("%d\n",dp[(1<<m)-1]);
	return 0;
}
posted @ 2019-10-21 21:30  DarkValkyrie  阅读(101)  评论(0编辑  收藏  举报