最开始学状压的时候...学长就讲的是这个题。当时对于刚好像明白互不侵犯和炮兵阵地的我来说好像在听天书......。因为我当时心里想,这又不是什么棋盘,咋状压啊?!后来发现这样的状压多了去了hhh。后来这道题就一直压着了,现在对状压明白了一点便来填坑。
我们注意到,团体队员数$N$比较大,而团体数$M$很小(不能称为乐队)。那么我们可以在$m$上下功夫,把它压成二进制串。开始想的状态是0表示这个团体还没站好,1表示这个团体已经站好了。看了看jtdalao的文章发现自己的状态是对的,但是转移嘛...,有点迷感觉。一步一步来。首先我们肯定要枚举当前的状态是什么,按照状压dp的套路,我们接下来要枚举下这一次新站好的是哪个队。转移时我们需要用在新队站好前的状态所需的次数+站成新队另需要的人数。我们默认大家都是排成一列紧跟在一个人之后的,所以我们需要求出当前已经排到谁了。所以我们还需要开一个数组前缀和来记录排队的信息。设$sum[i][j]$表示到$i$位置,$j$团体的人数。
转移有:
$f[i]$=$min$$($f[i^(1<<j)]$+$num[j]$-($sum[pos][j]$-$sum[pos-num[j]][j]$))。
因为当前新加的段之前也可能有本队队员,所以把他们减去。
初值,因为求最小,所以开始赋成极大,$f[0]=0$。(边界)
之后就比较好想了==。
$Code$
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 5 using namespace std; 6 7 int n,m,fAKe; 8 int f[1<<23],num[50],sum[100090][25]; 9 10 int main() 11 { 12 scanf("%d%d",&n,&m); 13 for(int i=1;i<=n;i++) 14 { 15 int x=0; 16 scanf("%d",&x); 17 num[x-1]++; 18 sum[i][x-1]++; 19 for(int j=0;j<m;j++) 20 sum[i][j]+=sum[i-1][j]; 21 } 22 fAKe=(1<<m)-1; 23 memset(f,0x3f,sizeof(f)); 24 f[0]=0; 25 for(int i=0;i<=fAKe;i++) 26 { 27 int pos=0; 28 for(int j=0;j<m;j++) 29 if(i&(1<<j)) pos+=num[j]; 30 for(int j=0;j<m;j++) 31 f[i]=min(f[i],f[i^(1<<j)]+num[j]-(sum[pos][j]-sum[pos-num[j]][j])); 32 } 33 printf("%d ",f[fAKe]); 34 return 0; 35 }