【题解】P3694 邦邦的大合唱站队

时间:2021-07-14
本文章向大家介绍【题解】P3694 邦邦的大合唱站队,主要包括【题解】P3694 邦邦的大合唱站队使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

【题解】P3694 邦邦的大合唱站队

题目传送门

题目描述

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

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

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

Solution

首先,想象最终队列的排列方式,其最多只有M!种

如111222233,或222233111,331112222……

于是我们可以用每一种排列方式分别与原数列的每一个位置依次进行比对,

显然,若原数列的相同位置上的数与最终数列上的数相同,则其一定不用出列

否则,则一定要出列

O(N)统计个数即可,总复杂度O(NM!)

考虑优化统计的复杂度

可以用前缀和sum[i][j]记录原数列前i个位置,共有多少个偶像来自团队j
再用一个num[i]表示团队i的总个数
这样每次统计O(M),总复杂度O(M*M!)

如何用状压dp呢

用s表示,当前哪些队伍已被排完。第i位是1,就表示第i个团队被排完

用f[s]表示前count(s)个队伍有哪些,排好他们的最少花费

转移时,枚举每一个可能的结尾队伍,按照刚才的方法算出花费,记录最小值

复杂度是(m*2^m)

那么为什么复杂度降低了呢

回忆我们一开始的全排列解法

比如1234和2134两个排列,他们的前两个数是相同的,只是顺序不一样,而在全排列解法中,我们却重复算了两遍

但是!!!实际上,在转移时我们根本不关心他前面具体的顺序,我们只需要知道他已经排了哪几个团队,以及这些团队的占用的总长度,而后者可以用前者计算

dp之所以比暴力快,就在于它及时统计状态信息,避免重复运算

所以我们可以想到以当前排好了哪几个队伍为状态,那么也很自然的就运用到了状态压缩

总结

  1. 有些题没有思路,可以采取想象最终答案,并与原数据进行比对的策略
  2. 在从暴力优化到正解的过程中,可以考虑哪些信息被重复统计,是否状态冗余,然后想办法压缩状态,或记录信息

Code

#include<bits/stdc++.h>
using namespace std;

inline int read()
{
	register int x=0,w=1;
	register char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if(ch=='-') {ch=getchar();w=-1;}
	while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();	}
	return x*w;
}
const int M=1e5+10;
int n,m,f[1<<20],sum[M][21],tot[21],len[1<<20];
int main()
{
    n=read();m=read();
    int x;
    for(int i=1;i<=n;++i)
    {
    	x=read();
    	for(int j=1;j<=m;++j) sum[i][j]=sum[i-1][j];
    	sum[i][x]++;
    	tot[x]++;
	}
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int i=1;i<(1<<m);++i)
	{
		for(int j=0;j<m;++j)
		{
			if(i>>j&1) len[i]+=tot[j+1];
		}
	}
	for(int i=1;i<(1<<m);++i)
	{
		for(int j=0;j<m;++j)
		{
			if(i>>j&1) f[i]=min(f[i],f[i^(1<<j)]+tot[j+1]-(sum[len[i]][j+1]-sum[len[i^(1<<j)]][j+1]) );
		}
	}
	cout<<f[(1<<m)-1];
	return 0;
}

原文地址:https://www.cnblogs.com/glq-Blog/p/15009567.html