解析:
举个例子,{2 1 4 3 1 2}
目标序列{* * * * * *}
假设我先把数字1放入目标序列尾部则有{* * * * 1 1} 交换次数为 5+6-2-5 = 4次(序列位置和的差)
然后我把数字2放入尾部则有{* * 2 2 1 1} 则是再像上面那样做差得出的是3+4-1-6 = 0显然是不对的。
因为1的位置的改变使第2个2的位置左移两位,所以应该是3+4-1-4 = 2次。
同理,再放入3和4,最后交换次数为6。
设p[i][i]为数字i在原始序列的位置和,p[i][j](i!=j)为数字j对数字i在序列位置上的总的左移量,dp[S]为在尾部放入S中的数字后的最小交换次数。
状态转移时通过枚举子状态取最小便可得出,具体看代码。
[code]:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;
int n,m,a[maxn],num[16];
LL dp[1<<16],p[16][16];
void init(){
int i,j,v;
memset(p,0,sizeof(p));
memset(num,0,sizeof(num));
for(i = 0;i < n;i++){
v = a[i];
num[v]++;
p[v][v] += i;
for(j = 0;j < m;j++){
if(j == v) continue;
p[v][j] += num[j];
}
}
}
void outputStatus(int S){
int cnt = 0;
while(cnt<m){
if(S&1) putchar('1');
else putchar('0');
S>>=1;
cnt++;
}
//putchar('\n');
}
void sol(){
//putchar('\n');
int i,j,S,Ed = 1<<m,S_;LL tmp_1,tmp_2;
for(S = 1;S < Ed;S++) dp[S] = 1e12;
dp[0] = 0;
for(S = 1;S < Ed;S++){
for(i = 0;i < m;i++){
if(!(S>>i&1)) continue;
//outputStatus(S);printf(" %d\n",i);
S_ = S^(1<<i);
tmp_1 = dp[S_]-p[i][i];
tmp_2 = 0;
//printf("%lld %lld -> ",tmp_1,tmp_2);
for(j = 0;j < m;j++){
if(!(S_>>j&1)) continue;
tmp_1 += p[i][j];
tmp_2 += num[j];
}
tmp_1 += num[i]*(2*(n-tmp_2)-1-num[i])/2LL;
//printf("%lld %lld\n",tmp_1,tmp_2);
dp[S] = min(dp[S],tmp_1);
}
}
printf("%lld\n",dp[Ed-1]);
}
int main(){
int i,j,cas;
scanf("%d",&cas);
for(int T=1;T<=cas;T++){
scanf("%d%d",&n,&m);
for(i = 0;i < n;i++){
scanf("%d",&a[i]);a[i]--;
}
init();
printf("Case %d: ",T);
sol();
}
return 0;
}