• 洛谷P3694 邦邦的大合唱站队【状压dp】


    状压dp

    应用思想,找准状态,多考虑状态和(f)答案数组的维数(这个题主要就是找出来状态如何转移)

    题目背景

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

    题目描述

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

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

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

    输入格式

    第一行(2)个整数(N)(M)

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

    输出格式

    一个整数,表示答案

    输入输出样例

    输入

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

    输出

    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\%)的数据,(Nle 20, M=2)

    对于(40\%)的数据,(Nle 100, Mle 4)

    对于(70\%)的数据,(Nle 2000, Mle 10)

    对于全部数据,(1le Nle 10^5), (Mle 20)

    分析

    看到这友好的乐队数范围,很容易就想到了状压dp,但是状态到底找哪个,记录答案的(f)数组开几维都是问题,我们来分析一下,题目中给出的乐队(M)的范围是(20),而状态压缩就是从小的范围入手的,所以(f)数组的状态那一维肯定是关于乐队的,再看题目中问的,询问的是要最少拿出来多少人,那么这个状态肯定就是第几个乐队入队的状态,记录的是当前状态下出队人数的最小,然后枚举最后一个位置的乐队,那么需不需要第二维呢?看起来是不需要的,因为我们每次转移都是从上一次当前乐队的人未放入到放入,然后加上当前乐队人数,减去增加的长度中当前乐队的人数,也就是算出前边需要出队的人数(这个人数用(sum)数组记录前缀和来实现),就是这一次需要拿出来的人数,然后每次转移都取一次(min),最终状态全为(1)的时候的(f)数组就是答案。状态转移方程如下:

    [f[i] = min(f[i xor (1<<(j-1) ] + num[j] - (sum[len][j]-sum[len-num[j]][j]),f[i]) ]

    其中(num)是第(j)个乐队的人数,(len)是到现在状态的队伍长度,预处理一下就可以。(sum)就是当前乐队在这一段中的人数。
    如果一共有(M)个乐队,最终答案就是(f[(1<<M)-1])
    总结一下数组代表的东西:
    (f[i])代表状态为(i)时出队的最小人数,(sum[i][j])表示前(i)长度里,(j)乐队的人数,(num[j])代表的就是(j)乐队的总人数。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 21;
    int f[1<<maxn];
    int num[maxn];
    int a[100005];
    int sum[100005][maxn];
    int n,m;
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;++i){
            cin>>a[i];
        }
        for(int i=1;i<=n;++i){
            num[a[i]]++;//记录每个乐队的总人数
            for(int j=1;j<=m;++j)sum[i][j] = sum[i-1][j];//初始化sum数组
            sum[i][a[i]]++;//求每个乐队人数的前缀和
        }
        memset(f,0x3f,sizeof(f));//初始化最大值
        f[0] = 0;//一个乐队都没有的时候取0人
        for(int i=1;i<(1<<m);++i){
            int len = 0;
            for(int j=1;j<=m;++j)if(i & (1<<(j-1)))len += num[j];//如果当前状态下取了j乐队的人,总人数就加上j乐队的人数
            for(int j=1;j<=m;++j){//枚举站在最后一个位置的乐队
                if(i & (1<<(j-1)))//效率优化,当前状态取了他再进行取min,不然取min没有意义
                      f[i] = min(f[i],f[i ^ (1<<(j-1))] + num[j] - sum[len][j]+sum[len - num[j]][j]);//状态转移,取第j个乐队要加上该乐队人数,减去这一段中本来就有的该乐队人数
            }
        }
        int ans = f[(1<<m)-1];
        cout<<ans;
    }
    
  • 相关阅读:
    Visual Studio中的键盘快捷键自动添加所需的使用声明
    Workflow异常
    理解Javascript_03_javascript全局观
    jquery插件开发方法
    JS在IE和FireFox之间的区别汇总
    IE与FireFox的js和css (杂记)
    CSS:IE与Firefox的CSS兼容大全
    CSS网页设计解决方案(Hacks & Issues)
    什么是标准站点(W3C标准)
    12个针对网页设计师的非常便利的CSS框架、模板和摘录网站
  • 原文地址:https://www.cnblogs.com/Vocanda/p/13196500.html
Copyright © 2020-2023  润新知