Light OJ 1194 Colored T-Shirts (状压DP)

75 篇文章 0 订阅

解析:

举个例子,{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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值