Luogu P3694 邦邦的大合唱站队

题目传送门

题目描述

N 个偶像排成一列,他们来自 M 个不同的乐队(1\le N \le 10^51\le M \le 20)。每个团队至少有一个偶像。

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

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

思路

首先,DP题的第一个难点就是判断它是否可以用DP解决。而我们通过题目可以观察到:题目解决的是每个组的排列问题,同时题目中的乐队数 M 非常的小,所以可以考虑用状压DP解决。
接下来,我们可以设 f_i 为状态为 i 时安排好队列所需的最小出列人数。然后,可以得到这样的状态转移方程:

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


其中的 j 代表当前正在处理的乐队的编号,而 f_{i^(1<<(j-1))} 代表将乐队 i 中的第 j 个人的状态取反(j=1 代表已出列,j=0 代表没有)。

为了解决这个问题,我们可以尝试将该合唱团的全部团员放在队尾。

此时,我们需要将不在 len-num_j+1 到 num_j 中的队员移进来(len为处理完成的人数,该区间就是此团队在排列完的队列中所在的位置)

需要的代价为 $num_j-sum_{len,j}+sum_{len-num_j,j}$,这里的 sum 代表前 i个位置中属于团队 j 的团员个数,我们可以在初始化中完成这个操作。

最后是damn代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n' 
using namespace std;
const int maxn=21,maxm=100005;
int n,m;
int f[1<<maxn],num[maxn],sum[maxm][maxn];
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        num[x]++;//每个乐队的成员数
        for(int j=1;j<=m;j++)sum[i][j]=sum[i-1][j];
        sum[i][x]++;//前i人中属于团队j的成员人数
    }
    memset(f,0x3f,sizeof(f));
    f[0]=0;//没有偶像出列
    for(int i=0;i<(1<<m);i++){
        int len=0;//已安排位置人数
        for(int j=1;j<=m;j++){
            if(i&(1<<(j-1)))len+=num[j];
        }
        for(int j=1;j<=m;j++){
            if(i&(1<<(j-1)))f[i]=min(f[i],f[i^(1<<(j-1))]+num[j]-sum[len][j]+sum[len-num[j]][j]);
        }
    }
    cout<<f[(1<<m)-1]<<endl;//状态为2^m-1时的最优解
    return 0;
}

posted @   iridescent94  阅读(3)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示