P3694 邦邦的大合唱站队/签到题(状压dp)

P3694 邦邦的大合唱站队/签到题

题目背景

BanG Dream!里的所有偶像乐队要一起大合唱,不过在排队上出了一些问题。

题目描述

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

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

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

输入输出格式

输入格式:

 

第一行2个整数N,M。

接下来N个行,每行一个整数a_i(1\le a_i \le M)ai​​(1ai​​M),表示队列中第i个偶像的团队编号。

 

输出格式:

 

一个整数,表示答案

 

输入输出样例

输入样例#1:
12 4
1
3
2
4
2
1
2
3
1
1
3
4
输出样例#1:
7

说明

【样例解释】

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

【数据规模】

对于20%的数据,N\le 20, M=2N20,M=2

对于40%的数据,N\le 100, M\le 4N100,M4

对于70%的数据,N\le 2000, M\le 10N2000,M10

 

/*
状压dp
状态:dp[i]表示i状态下最小的出列(不一致)的个数。
比如dp[1101]表示从头到位为1/3/4乐队的偶像的最小出列个数。

预处理sum[i][j]表示前i个人中j种的数量
dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+(r-l-(sum[r][j]-sum[l][j])));
*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

#define inf 100000000
#define N 100007

using namespace std;
int n,m;
int a[N],dp[(1<<21)+1],sum[N][21];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        a[i]--;
        for(int j=0; j<m; j++)
        {
            sum[i][j]=sum[i-1][j];
            if(j==a[i]) sum[i][j]++;
        }
    }
    for(int i=0; i<(1<<m); i++) dp[i]=inf;
    dp[0]=0;
    for(int i=0; i<(1<<m); i++)
    {
        int Sum=0;
        for(int j=0; j<m; j++)
            if((1<<j)&i) Sum+=sum[n][j];
        for(int j=0; j<m; j++)
        {
            if((1<<j)&i) continue;
            int num=sum[n][j];
            int r=Sum+num;
            int l=Sum;
            dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+(r-l-(sum[r][j]-sum[l][j])));
        }
    }
    printf("%d\n",dp[(1<<m)-1]);
    return 0;
}

 

对于全部数据,1\le N\le 10^5, M\le 201N105​​,M20

posted @ 2017-09-24 17:27  安月冷  阅读(354)  评论(0编辑  收藏  举报