P3694 邦邦的大合唱站队

题面

看范围,N 较大, M 符合状压的范围。

f[S] 表示 编号为L(L是S的一位的团队)且这一位为1 的团队已经摆好了.例如1011就是将1,2,4团队的歌手都摆好的最小值。

我们考虑如何转移。在Dp转移中,我们都是通过决策,将未知的状态由已知的状态推出,那么决策显然就是将一个团队的歌手摆好需要多摆几个人

假设现在状态集合为S,那么少一个歌手团队的集合就是S ^ (1 << j-1) ,这个集合我们设为K,那么f[S] 就由 f[K]转移。

我们就让K集合比S集合少的人数累计到答案里。特别显然的是,肯定不需要所有人都离开位置,以前就在那个位置上的可以不移动,所以在减去原先就在那个范围的人。

我们需要预处理很多东西

1.我们需要预处理sum[i]表示i团队一共有多少人,预处理简单

2.我们需要预处理1 - j的这些位置i团队有多少人,q[i][j]

3.我们需要预处理集合S中,某些位是1的歌手的和

完整的方程就是

f[S] = f[K] + sum[j] -(q[j][s[i]] - q[j][s[K]]);

K = S ^ (1 << j-1)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
const int M = 22;
int f[1 << M];
int n, m;
int a[N], q[M][N];
//前缀和,q[M][N];q[i][j] 1 - j 有多少个i团队的人
//The number of singers who work in the team i that singing in (1 to j) 
int sum[M];
int S[1 << M];

int main(){
    cin >> n >> m;
    for(int i = 1;i <= n;++ i)
    cin >> a[i];
    for(int i = 1;i <= n;++ i){
        sum[a[i]] ++;
        q[a[i]][i] = 1;
    }
    for(int i = 1;i <= n;++ i){
        for(int j = 1;j <= m;++ j)
        q[j][i] += q[j][i-1];
    }
    for(int i = 1;i < 1 << m;++ i){//O(2^m * m)
        int k = 0;
        int tmp = i;
        while(tmp){
            ++ k;
            if(tmp & 1) S[i] += sum[k];
            tmp >>= 1;
        }
    }
    memset(f, 0x3f, sizeof  f);
    f[0] = 0;
    for(int i = 1;i < 1 << m;++ i){
        for(int j = 1;j <= m;++ j){
            int k = i ^ (1 << (j - 1));
            f[i] = min(f[i], f[k] + sum[j] - (q[j][S[i]] - q[j][S[k]]));
        }
    }
    cout << f[(1<<m)-1];
    
    return 0;
}
原文地址:https://www.cnblogs.com/Shu-Kuang/p/13325037.html