[Luogu] P3694 邦邦的大合唱站隊
阿新 • • 發佈:2020-11-23
Description
\(N\)個偶像排成一列,他們來自\(M\)個不同的樂隊。每個團隊至少有一個偶像。
現在要求重新安排佇列,使來自同一樂隊的偶像連續的站在一起。重新安排的辦法是,讓若干偶像出列(剩下的偶像不動),然後讓出列的偶像一個個歸隊到原來的空位,歸隊的位置任意。
請問最少讓多少偶像出列?\((1\le{N}\le10^5,M\le20)\)
Solution
注意到全排列是過不去的,考慮狀壓。
設\(dp[i]\)表示\(i\)這個狀態(二進位制)下為\(1\)的樂隊都排好位置時最小的出隊人數。轉移就是由\(i\)中把一個樂隊排到最後來轉移。然後用字首和來快速判斷一段區間內某一樂隊的偶像數量。
Code
#include <bits/stdc++.h> using namespace std; int n, m, a[100005], dp[1050005], sum[100005][25], tot[25]; int read() { int x = 0, fl = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();} while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();} return x * fl; } int main() { n = read(); m = read(); memset(dp, 0x3f, sizeof(dp)); dp[0] = 0; for (int i = 1; i <= n; i ++ ) { a[i] = read(); tot[a[i]] ++ ; for (int j = 1; j <= m; j ++ ) sum[i][j] = sum[i - 1][j]; sum[i][a[i]] ++ ; } for (int i = 0; i <= (1 << m) - 1; i ++ ) { int cnt = 0; for (int j = 1; j <= m; j ++ ) if (i & (1 << (j - 1))) cnt += tot[j]; for (int j = 1; j <= m; j ++ ) if (i & (1 << (j - 1))) dp[i] = min(dp[i], dp[i - (1 << (j - 1))] + sum[n][j] - sum[cnt][j] + sum[cnt - tot[j]][j]); } printf("%d\n", dp[(1 << m) - 1]); return 0; }