题目大意
给定一个长度为n的序列,每个数的大小为1~20,然后问可以相邻的两个数两两交换,问最小交换多少次可以使得相同的数都聚集在一块。
思路
可以用一个状态0 <= S <= (1<<20-1) 代表集合中的数都摆放好
d(S)代表最小移动次数
然后我们有一个状态转移策略,每次往S中丢一个新元素,且每次都把他放到前面,所需的步数可以算出来,cnt[i][j]就是预处理的假设只含有i,j两个元素,i要放到j前面所需的最小步数。
代码
#include <map>
#include <set>
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int S_sz = (1 << 20);
ll d[S_sz], cnt[22][22], tot[22];
int n;
int main() {
//freopen("/Users/maoxiangsun/MyRepertory/input.txt", "r", stdin);
scanf("%d", &n);
for(int i = 0; i < (1 << 20); i++) {
d[i] = 1e16;
}
for(int i = 0; i < n; i++) {
int x;scanf("%d", &x); x--;
for(int j = 0; j < 20; j++) cnt[x][j]+=tot[j];
tot[x]++;
}
d[0] = 0;
for(int S = 0; S < S_sz; S++) {
for(int i = 0; i < 20; i++) {
if((S>>i)&1) continue;
ll res = 0;
for(int j = 0; j < 20; j++) {
if((S>>j) & 1)res+=cnt[i][j];//ok
}
d[ S | (1 << i) ] = min(d[ S | (1 << i) ], d[S] + res);
}
}
cout<<d[S_sz-1]<<endl;
return 0;
}