邦邦的大合唱站队

洛咕

题意:N个偶像排成一列,他们来自M个不同的乐队.每个团队至少有一个偶像.现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起.重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意.问最少让多少偶像出列?(n<=1e5,m<=20.)

想了半个小时,没有思路,丢给了@(lyh),几分钟后,"我只想到了(70)分做法",又过了几分钟,"我会了(!!!)",再过几分钟,"我切了(!!!)".又是被包菜的一天.

分析:分析一下样例,发现就是确定出一种排列,然后跟原先打乱的队列去一一比较,不同的就产生1的贡献.例如,

1 3 2 4 2 2 1 2 3 1 1 3 4

3 3 3 4 4 2 2 2 1 1 1 1 1

不同的位置有7个,则这种方案的答案就是7.

所以(70)分做法就是暴力枚举出所有的排列,然后一一比较取最小值.

正解:(m<=20),状压(!!!).设(f[i])表示已经排好的乐队的集合状态为i时的最少出列数.

(f[i]=min(f[i],f[i)^((1)<<((j-1))]+calc(j))),(calc(j))表示当前把编号为(j)的排进去产生的贡献,一一比较的话复杂度太高.

可以预处理出(sum[i][j])表示前i个位置中编号为j的出现的次数.(pos[i])表示排好的乐队状态为(i)时排到了哪个位置.(pos[i]=sum_{j=1}^m sum[n][j],if(i)&((1)<<((j-1))==1))

所以(calc(j)=sum[n][j]-(sum[pos[i]][j]-sum[pos[last]][j])),其中(last=i)^((1)<<((j-1)))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e5+5;
int a[N],sum[N][21],f[1050000],pos[1050000];
int main(){
	int n=read(),m=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			sum[i][j]=sum[i-1][j];
			if(a[i]==j)++sum[i][j];
		}
	}
	for(int i=1;i<(1<<m);++i)
		for(int j=1;j<=m;++j)
			if(i&(1<<(j-1)))pos[i]+=sum[n][j];
	memset(f,0x3f,sizeof(f));f[0]=0;
	for(int i=1;i<(1<<m);++i){
		for(int j=1;j<=m;++j){
			if(i&(1<<(j-1))){
				int last=i^(1<<(j-1));
				f[i]=min(f[i],f[last]+sum[n][j]-(sum[pos[i]][j]-sum[pos[last]][j]));
			}
		}
	}
	printf("%d
",f[(1<<m)-1]);
	return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11733199.html