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;
}

浙公网安备 33010602011771号