【CF1215E】Marbles【状压DP】

本文介绍了一种解决序列排列问题的高效算法,通过状态压缩和动态规划实现,适用于长序列和多值域场景,如将相同值连成一片的最少操作次数问题。

传送门

题意:给一个长为 N N N的序列 a a a,每次操作交换两个相邻位置,求最少操作次数使得所有相同的值连成一片。

N ≤ 400000 N \leq 400000 N400000, a i ≤ 20 a_i \leq20 ai20

我们发现 a i a_i ai很小,盲猜单独考虑

我们重新确认一个宏大的时空观

把所有位置按值分组,然后一组一组加进去。在某一组没有加之前,这个位置是不存在的,即1 3 2在没有加3时,1 2是相邻的。

c n t [ i ] [ j ] cnt[i][j] cnt[i][j]表示只有 i i i j j j两组数时,把所有 i i i移到 j j j的前面的最小操作次数

这个可以暴力枚举 i i i j j j,然后双指针即可

考虑状压

d p [ S ] dp[S] dp[S]表示当前加入的数的状态为 S S S的最小操作次数 我们新加一个数时,把散装的都移到最前面。因为 c n t cnt cnt是单独考虑的,所以加起来就可以了

d p [ S ] = min ⁡ i ∈ S { d p [ S − { i } ] + ∑ j ∈ S , i ≠ j c n t [ i ] [ j ] } dp[S]=\min_{i \in S}\{dp[S-\{i\}]+\sum_{j\in S,i\neq j}cnt[i][j]\} dp[S]=iSmin{dp[S{i}]+jS,i=jcnt[i][j]}

最后的 d p [ 2 n − 1 ] dp[2^n-1] dp[2n1]即答案

复杂度 O ( n a 2 + 2 a a 2 ) O(na^2+2^aa^2) O(na2+2aa2)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
using namespace std;
typedef long long ll;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
vector<int> v[20];
ll cnt[20][20],dp[1<<20];
int main()
{
	int n=read();
	for (int i=1;i<=n;i++) v[read()-1].push_back(i);
	for (int i=0;i<20;i++)
		for (int j=0;j<20;j++)
			if (i!=j)
			{
				int pos=-1;
				for (int k=0;k<v[i].size();k++)
				{
					while (pos+1<v[j].size()&&v[j][pos+1]<v[i][k]) ++pos;
					cnt[i][j]+=pos+1;
				}
			}
	dp[0]=0;
	for (int s=1;s<(1<<20);s++)
	{
		dp[s]=1e18;
		for (int i=0;i<20;i++)
			if (s&(1<<i))
			{
				ll sum=0;
				for (int j=0;j<20;j++)
					if (s&(1<<j))
						sum+=cnt[i][j];
				dp[s]=min(dp[s],dp[s^(1<<i)]+sum);					
			}	
	}
	cout<<dp[(1<<20)-1];
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值