题意:给一个长为 N N N的序列 a a a,每次操作交换两个相邻位置,求最少操作次数使得所有相同的值连成一片。
N ≤ 400000 N \leq 400000 N≤400000, a i ≤ 20 a_i \leq20 ai≤20
我们发现 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]=i∈Smin{dp[S−{i}]+j∈S,i=j∑cnt[i][j]}
最后的 d p [ 2 n − 1 ] dp[2^n-1] dp[2n−1]即答案
复杂度 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;
}
本文介绍了一种解决序列排列问题的高效算法,通过状态压缩和动态规划实现,适用于长序列和多值域场景,如将相同值连成一片的最少操作次数问题。
762

被折叠的 条评论
为什么被折叠?



