题目
zz是一位大理石收藏家,他在家里收藏了n块各种颜色的大理石,第i块大理石的颜色为ai。但是zz觉得这些石头在家里随意摆放太过凌乱,他希望把所有颜色相同的石头放在一起。换句话说,zz需要对现有的大理石重新进行排列,在重新排列之后,对于每一个颜色j,如果最左边的颜色为j的大理石是第l块大理石,最右边的颜色为j的大理石是第r块大理石,那么从第l块大理石到第r块大理石,这些石头的颜色都为j。
由于这些大理石都比较重,zz无法承受这些大理石的重量太久,所以他每次搬运只能交换相邻的两块大理石。请问,zz最少需要进行多少次搬运?
输入格式
第一行输入一个数字n(2≤n≤4*10^5),表示大理石的总数。
第二行输入n个数字a1,a2…,an(1≤ai≤20)表示第i块大理石的颜色为ai。
输出格式
输出zz最少需要搬运的次数。
思路
n很大,但是颜色少啊,很明显的一个状压DP的题,状态的表示0即为当前
位置的颜色没有完成,1则表示为完成了
定义一个数组表示
g
[
25
]
[
25
]
g[25][25]
g[25][25]表示i号颜色前j号颜色一共有多少个,
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
i
−
j
]
+
c
o
s
t
(
i
,
j
)
)
dp[i]=min(dp[i],dp[i-j]+cost(i,j))
dp[i]=min(dp[i],dp[i−j]+cost(i,j))
代码
#include <iostream>
#include <cstring>
using namespace std;
#define int long long
int n;
int a[400005];
int cost[400005][25]; // i为前颜色为j有多少个
int c[25][25]; //表示第i个颜色前有多少个j颜色
int dp[(1 << 21)];
int solve_cost(int col, int t) {
int ans = 0;
for (int i = 0; i <= 20; i++) {
if (!(t >> i & 1) && i != col)
ans += c[col][i];
}
return ans;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i]--;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 20; j++) cost[i][j] = cost[i - 1][j];
cost[i][a[i]]++;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 20; j++) {
c[a[i]][j] += cost[i][j];
}
}
memset(dp, 127, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= (1 << 20); i++) {
for (int j = 0; (1 << j) <= i; j++) {
if ((i & (1 << j)) != 0) {
dp[i] = min(dp[i], dp[i - (1 << j)] + solve_cost(j , i - (1 << j)));
}
}
}
cout << dp[(1 << 20) - 1];
return 0;
}