题目链接:https://codeforces.com/contest/1215/problem/E
题目大意:
有一个序列,每次可以交换相邻的数字,问最少多少次交换能使得相同的数字呆在一起
题目思路:
其实距离想出来就差一点点了…最后没想出这个代价怎么处理,后来还是放弃了…首先看到颜色只有20,马上想到状压DP, d p [ i ] dp[i] dp[i]的 i i i转换为二进制以后,相应的位1就说明已经排好了这些颜色。然后需要一个预处理,就是 p [ i ] [ j ] p[i][j] p[i][j],表示在所有 i i i前面有多少个 j j j,因为如果一个数字想移到最开头,那么最少的花费就是所有这个数字移动过程中一路上遇到多少其他数字,他们是必须要碰到的。这有啥用呢?首先知道这个数字一开始就移到最开头需要的代价,但是可以发现,如果有人已经排好队了,那它可能就没必要花这么多步了,因为它在被推后的过程中换了位置。这跟刚开始处理的有啥区别呢?现在已经排好的数字乖乖在前头呆着,不影响合体!所以就需要把前面已经排好的贡献去掉,就是现在加入当前数字的代价,只要枚举每个状态中的1作为最后一个加入的数字,然后更新就行。这题的难点就在于这个贡献的计算,没想到这题居然能支持这么高的复杂度,还一直想着树状数组…最后复杂度高达4e8居然1s不到就跑完了,惹不起惹不起,cf评测姬恐怖如斯…
题目思路:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
const int MAXN = 2e6+5;
ll a[MAXN],dp[MAXN],p[25][25],num[25],sum[25];
int main()
{
int n;
while(~scanf("%d",&n)){
dp[0]=0;
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
rep(i,1,n){
scanf("%I64d",&a[i]);
rep(j,1,20){
if(j==a[i])continue;
p[a[i]][j]+=num[j];
}
num[a[i]]++;
}
rep(i,1,20){
rep(j,1,20)sum[i]+=p[i][j];
}
int limit=(1<<20)-1;
rep(i,1,limit){
dp[i]=1ll<<62;
rep(j,0,19){
if((i>>j)&1){
int temp=i^(1<<j);
ll m=sum[j+1];
rep(k,0,19){
if((i>>k)&1){
m-=p[j+1][k+1];
}
}
dp[i]=min(dp[i],dp[temp]+m);
}
}
}
printf("%I64d\n",dp[limit]);
}
return 0;
}