传送门
题意:给一个长为NNN的序列aaa,每次操作交换两个相邻位置,求最少操作次数使得所有相同的值连成一片。
N≤400000N \leq 400000N≤400000,ai≤20a_i \leq20ai≤20
我们发现aia_iai很小,盲猜单独考虑
我们重新确认一个宏大的时空观
把所有位置按值分组,然后一组一组加进去。在某一组没有加之前,这个位置是不存在的,即1 3 2
在没有加3
时,1 2
是相邻的。
设cnt[i][j]cnt[i][j]cnt[i][j]表示只有iii和jjj两组数时,把所有iii移到jjj的前面的最小操作次数
这个可以暴力枚举iii和jjj,然后双指针即可
考虑状压
设dp[S]dp[S]dp[S]表示当前加入的数的状态为SSS的最小操作次数 我们新加一个数时,把散装的都移到最前面。因为cntcntcnt是单独考虑的,所以加起来就可以了
即
dp[S]=mini∈S{dp[S−{i}]+∑j∈S,i≠jcnt[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]}
最后的dp[2n−1]dp[2^n-1]dp[2n−1]即答案
复杂度O(na2+2aa2)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;
}