Codeforces Round #585 (Div. 2) E. Marbles (状压dp)

8 篇文章 0 订阅

题目链接

题意:给你一个长度为n的序列,其中最多有20种颜色(用数字表示)。每次可以交换相邻两个元素,求最少的交换次数,使得相同数字的元素都合并在一起。

题解:赛后学了一波贪心AC的题解,没想到加强了数据wa了。于是转学状压dp。

  • 首先进行预处理。设cnt[ i ][ j ],表示第 i 种颜色全部放到第 j 种颜色前面所需的操作次数
  • 从前开始枚举,设当前位置颜色为x ,那么cnt[ x ][ j ] += num[ j ],将x颜色与之前的所有j颜色交换一次。

一开始的贪心思路错在会使颜色的相对位置出现矛盾。身为初学者的我看大佬们的博客半天看不懂,或许你也如此,所以我尽量说详细点。

  • 正解应该要枚举二十种颜色的相对位置,然后计算需要操作次数最小的答案。
  • 有20!种情况,使用状压dp可以优化到2^20种。
  • 设dp[ s ](0 <=s<= 2^20 - 1)表示达到第 s 种状态需要的最少交换次数。
  • 状态表示的二进制第 i 位如果为 1 代表选择了第 i 种颜色。
  • 那么 100011这个状态就代表了选择了第1,2,6种颜色。
  • 而dp[100011]就表示为了达到这种状态需要的最少操作次数,也就是排列三种颜色的先后顺序。
  • 这个状态可以由 100010 ,100001,000011转移而来,转移分别为
  • 100010(之前选择了2,6颜色,现在将1颜色放他们前面)
  • 100001(之前选择了1,6颜色,现在将2颜色放在他们前面)
  • 000011(之前选择了1,2颜色,现在将6颜色放在他们前面)
  • 所以dp[s] = min(dp[s] , dp[s'] + tmp),tmp表示需要交换的次数。

这样一步一步递归上去,最后求出dp[1111111...] 就是答案了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = (1<<20), N = 25;
ll dp[mx], tot[25], cnt[N][N]; 
int main()
{
	int n, x;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i){
		scanf("%d", &x);
		x--;
		for(int j = 0; j < 20; ++j)//计算x元素放到j元素前需要的交换次数 
			cnt[x][j] += tot[j];
		tot[x]++;
	} 
	memset(dp, 0x3f, sizeof dp);
	dp[0] = 0;
	for(int i = 0; i < mx; ++i)
	for(int j = 0; j < 20; ++j)
	if(!((i>>j) & 1)){ // 如果第(j + 1)为0表示可以添加第(j + 1)种颜色 
		ll tmp = 0;
		
		for(int k = 0; k < 20; ++k)//将第j种颜色放在此状态里所有颜色的前面 
			if((i>>k) & 1) //求出需要交换的次数 
				tmp += cnt[k][j];
				
		//i ^ (1<<j) 将 i的第(j + 1)位变成1 
		dp[i ^ (1<<j)] = min(dp[i ^ (1<<j)], dp[i] + tmp);
	} 
	printf("%lld\n",dp[mx - 1]);
 } 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值