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

· · 题解

题目

P3694 邦邦的大合唱站队

思路

状压 dp。因为乐队数 m \leq 20,考虑将现在排好了哪些乐队压为二进制。

如共 8 支乐队已经排好了1,34 号乐队那么对应的二进制数为 00001101

$sum[i][j]$ 表示原来的顺序中前 $i$ 个人中有几个是 $j$ 号乐队的。 $f[i]$ 表示排好了 $i$ 这些乐队最少需要多少人出队。 动态转移方程如下: ```cpp if (i & (1 << (j - 1))) { int l = s[i ^ (1 << j - 1)], r = s[i]; f[i] = min(f[i], f[i ^ (1 << (j - 1))] + sum[n][j] - sum[r][j] + sum[l][j]); } ``` 含义为,已经排好了 $i$ 这些乐队,$j$ 是其中的一支考虑将 $j$ 放到已经排好的这些乐队的最后那么就可以用排好了 $i \oplus j$ 这些乐队的最优情况 $f[i \oplus (1 << (j - 1))]$ 以及不在 $[s[i \oplus (1 << (j - 1))] + 1, s[i]]$ 这个区间中的 $j$ 号乐队的人数 $sum[n][j] - sum[s[i]][j] + sum[s[i \oplus (1 << j - 1)]][j]$ 来更新 $f[i]$。 ### Code ```cpp #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> #define inf 2147483647 #define MAXN (1 << 20) + 1 int n, m, sum[100001][21]; int s[MAXN], f[MAXN]; int min(int a, int b) { return a < b ? a : b; } int main() { scanf("%d %d", &n, &m); for (int i = 1, x; i <= n; ++i) { scanf("%d", &x); for (int j = 1; j <= m; ++j) { sum[i][j] = sum[i - 1][j]; } ++sum[i][x]; } for (int i = 0; i < (1 << m); ++i) { int d = i, cnt = 0; while(d) { ++cnt; if(d & 1) s[i] += sum[n][cnt]; d >>= 1; } f[i] = inf; } f[0] = 0; for (int i = 0; i < (1 << m); ++i) { for (int j = 1; j <= m; ++j) { if (i & (1 << (j - 1))) { int l = s[i ^ (1 << j - 1)], r = s[i]; f[i] = min(f[i], f[i ^ (1 << (j - 1))] + sum[n][j] - sum[r][j] + sum[l][j]); } } } printf("%d\n", f[(1 << m) - 1]); return 0; } ```