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

· · 题解

题解开始前请允许我高呼一声:BangDream天下第一!

BangDream国服开始公测啦,有没有小伙伴一起来玩啊OwO~

回归正题。

首先注意到这是一道偶像多达五个数量级的LIVE题目,要么贪心要么DP,又注意到虽然N很大,但总乐队数仅有20,可考虑对其状压。

我们可以设状态S为当前已经排列好的乐队编号集合,那么先不管这几个乐队怎么排列,她们的人数总和是一定的(不妨设其为l[S]),那她们从第一个位子站起,所占有的区间一定是[~1~,~l[S]~]

那我们枚举站在最后一个位置的乐队(不妨设人数为num[i]),就同样可以确定它所对应的区间是[~l[S]-num[i]+1~,~l[S]~],那么原先在这个范围内的,该乐队的偶像不用出列,其他人全部出列就可以了。

其中,num[i],l[S],还有s[i][j]表示初始状态下前i个位子中编号为j的乐队的人数,这三个信息都可以由初始化得到。

于是便得到了最终的转移方程,设f[S] 为状态S时所出列的最小人数,那么对于\forall j\in S,满足

f[S]=min(f[~S~xor~(1<<j)~]+num[j]-s[r][j]+s[l][j]

其中l,r表示上文提到的最后一个乐队所对应的区间。

然后就可以愉快的AC辣,上丑陋的代码:

#include <iostream>
#include <cstring>
using namespace std;

int n,m,x,f[2000000];
//首先枚举人肯定傻逼TLE,数据范围只能用状压
//设f[i]--> 状态I表示从左往右被标记的乐队已排好;
//f[i]表示状压为I时所出列最少人数。 
int s[100010][30],num[30],sm[2000000]; 

bool d(int s,int x){
    return (s&(1<<(x-1)));
}

void dfs(int x,int s,bool b)
{
    if (x==m) return;
    if (b) sm[s|(1<<x)]=sm[s]+num[x+1],dfs(x+1,s|(1<<x),1),dfs(x+1,s|(1<<x),0);
    else dfs(x+1,s,1),dfs(x+1,s,0); 
}

int main()
{
    cin>>n>>m;
    for (int i=1;i<=n;i++) {
        cin>>x; 
        for (int j=1;j<=m;j++) s[i][j]=s[i-1][j];
        s[i][x]++; num[x]++;
    }
    dfs(0,0,0); dfs(0,0,1);
    memset(f,0x3f,sizeof(f)); f[0]=0;
    for (int i=1;i<(1<<m);i++)
    {
        for (int j=1;j<=m;j++) if (d(i,j)) {
            int l=sm[i^(1<<j-1)],r=sm[i];
            f[i]=min(f[i],f[i^(1<<(j-1))]+(r-l)-(s[r][j]-s[l][j]));
        }   
    }
    cout<<f[(1<<m)-1]<<endl;
    return 0;
}