习题:大理石(状压DP)

题目

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[ij]+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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值