sjtu1590 强迫症

Description

BS96发布了一套有\(m\)个band柄绘的新badge,kuma先生想要拿到04的badge于是进行了抽抽抽。
kuma先生一共抽了\(n\)个badge。他把所有的badge排成一排来统计战果,出于强迫症他希望把所有相同band的badge放在一起。
kuma先生整理badge的方法是交换\(2\)个相邻的badge,现在他想知道他最少交换多少次可以达成目的。

Input

有多组数据,第一行一个数\(T\)表示数据组数,接下来每组数据有\(2\)行。
第一行两个数\(n,m\)
第二行\(n\)个数,表示每个badge的band编号。

Output Format

对于每组数据输出一行,形如”Case #X:Y”。X为数据组数,从\(1\)开始,\(Y\)为最少的交换次数。

Sample Input

3
4 2
1 2 1 2
6 4
2 1 4 3 1 2
8 6
1 3 2 5 5 4 5 2

Sample Output

Case #1: 1
Case #2: 6
Case #3: 5

Hints

\(40\%\), \(n \le 100, m \le 6\)
\(70\%\), n \le 1000, m \le 14
\(100\%\), n \le 100000, m \le 18

首先确定了最后次序之后,这个过程就是一个冒泡排序,最少交换次数即为逆序对数目。于是这道题目便变成了一道排列dp题目。我们可以用状态压缩dp来解决。
\(f[i]\)为状态为\(i\)时的最少逆序对数目。这个状态表示若\(a\)在集合\(i\)中,则\(a\)的最终位置已经确定排在前面一块。现在考虑我们将一个不在集合中的元素\(b\)加入集合中,我们只需要考虑\(b\)对逆序对的贡献,由于集合中已有元素最后都会放在\(b\)的前面,所以我们只需要计算在原数组中有多少\((p,q),q>p\),其中\(A_p = b,A_q \in i\)。对于特定的\(a,b\),这个贡献是可以预处理的。于是算法复杂度\(O(TM^22^M)\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;

typedef long long ll;
#define maxn (100010)
#define maxm (20)
int T,N,M,A[maxn],suf[maxm][maxn],tmp[maxm]; ll f[1<<maxm],cor[maxm][maxm]; bool vis[1<<maxm];
     
inline int read()
{
    int F = 1,ret = 0; char ch;
    do ch = getchar(); while (!(ch >= '0'&&ch <= '9')&&ch != '-');
    if (ch == '-') F = -1,ch = getchar();
    do ret = ret*10+ch-'0',ch = getchar(); while (ch >= '0'&&ch <= '9');
    return F*ret;
}
     
inline void ready()
{
    for (int i = 1;i <= N;++i) suf[A[i]][i] = 1;
    for (int i = N-1;i;--i) for (int j = 1;j <= M;++j) suf[j][i] += suf[j][i+1];
    for (int i = 1;i <= N;++i)
        for (int j = 1;j <= M;++j)
            cor[A[i]][j] += (ll)suf[j][i];
}
          
inline void init()
{
    memset(vis,false,1<<M);
    memset(cor,0,sizeof(cor));
    for (int i = 1;i < (1<<M);++i) f[i] = 1LL<<50;
    for (int i = 0;i < M;++i) f[1<<i] = 0;
    for (int i = 1;i <= M;++i) memset(suf[i],0,4*(N+1));
    for (int i = 0;i < M;++i) vis[1<<i] = true; 
}
     
int main()
{
    freopen("1590.in","r",stdin);
    freopen("1590.out","w",stdout);
    T = read();
    for (int Case = 1;Case <= T;++Case)
    {
        printf("Case #%d: ",Case);
        N = read(),M = read(); init();
        for (int i = 1;i <= N;++i) A[i] = read();
        ready();
        for (register int i = 1,nn;i < (1<<M)-1;++i)
        {
            nn = 0;
            for (register int j = 0;j < M;++j)  if (i&(1<<j)) tmp[++nn] = j+1;
            for (register int j = 0;j < M;++j)
                if (!(i&(1<<j)))
                {
                    ll sum = f[i];
                    for (register int k = 1;k <= nn;++k) sum += cor[j+1][tmp[k]];
                    if (f[i|(1<<j)] > sum) f[i|(1<<j)] = sum;
                }
        }
        printf("%lld\n",f[(1<<M)-1]);
    }
    fclose(stdin); fclose(stdout);
    return 0;
}

转载于:https://www.cnblogs.com/mmlz/p/6172124.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值