P3694 邦邦的大合唱站队(状压 dp)

目录

Description

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

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

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

State

\(1<=n<=10^5\)

\(1<=m<=20\)

Input

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

Output

7

Solution

利用状压进行排队,假设当前状态 \(S=1010\) 表示 \(2\ 4\) 已经排列好的最小出队人数

然后用 \([|S|+1, |S_1|]\) 这个区间内 \(1\) 的数量来更新 \(dp[S_1]\) 以及用 \([|S|+1, |S_3|]\) 区间内 \(3\) 的数量更新 \(dp[S_3]\)


在处理这个问题之前还需要一个工具 \(f[i][j]\) :表示在 \([i, i+tax[j] - 1]\) 这个区间上 \(j\) 的出现次数

\(tax[j]\) 表示在整个区间上 \(j\) 的出现次数


Code

const int M = 20 + 5;
const int S = (1 << 20) + 5;
const int N = 1e5 + 5;
 
    int n, m, k, _;
    int a[N];
    int dp[S];
    int sum[S], tax[M];
    int f[N][M];

int calc(int x)
{
    int bit = 1, ans = 0;
    while(x){
        if(x & 1) ans += tax[bit];
        x >>= 1;
        bit ++;
    }
    return ans;
}

signed main()
{
    // IOS;
    while(~ sdd(n, m)){
        rep(i, 1, n) sd(a[i]), tax[a[i]] ++;
        int all = (1 << m);
        for(int i = 1; i < all; i ++){
            sum[i] = calc(i);
        }
        for(int j = 1; j <= m; j ++){
            for(int i = 1; i <= tax[j] - 1; i ++){
                if(a[i] == j) f[0][j] ++;
            }
        }
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                f[i][j] = f[i - 1][j];
                if(a[i - 1] == j) f[i][j] --;
                if(a[i + tax[j] - 1] == j) f[i][j] ++;
            }
        }
        ms(dp, inf);
        dp[0] = 0;
        for(int s = 0; s < all; s ++){
            for(int i = 0; i < m; i ++){
                if((s & (1 << i)) == 0){
                    int len = sum[s | (1 << i)] - sum[s] - f[sum[s] + 1][i + 1];
                    dp[s | (1 << i)] = min(dp[s | (1 << i)], dp[s] + len);
                }
            }
        }
        pd(dp[all - 1]);
    }
    // PAUSE;
    return 0;
}
posted @ 2021-10-30 19:27  Bcoi  阅读(62)  评论(0)    收藏  举报