SkyWT / 博客 / CodeForces 1215E Marbles 题解

CodeForces 1215E Marbles 题解

2019 年 9 月 28 日 11:10


文章目录

Description

给出一个长度为 n 的数列,2n41052\leq n\leq 4\ast 10^5,每个数字 aia_i 都在 [1,20][1,20] 内。
可以对这个数列中相邻的两个数字交换位置,最终要使得相同的数字都在一起。
求最小交换次数。

Link

Tags

状压DP

Analysis

ai[1,20]a_i \in [1,20],很明显可以用状压 DP。

首先可以发现一个重要的性质:如果交换相邻的两数,其他所有数字的相对位置是不变的。根据这个性质,在交换的时候我们就可以分开考虑每一种数字。
数字只有 20 种,则我们可以一种数字一种数字考虑,设 F(mask)F(mask) 为状态为 maskmask 的最小交换位置。maskmask 对应位上的 0 或 1 分别表示某种数字是否已经考虑。
接下来考虑状态转移,设现在状态已经是 maskmask,需要新增的数字是 jjmaskmask 中对应位为 0)。则我们可以直接把所有 jj 移动到最左边(如果最优解中此时 jj 不在最左边怎么办?则其他状态会涵盖这个方案)。
为了快速做到这一点,预处理一个数组 cnt(i,j)cnt(i,j) 表示把所有 ii 数字移动到所有 jj 数字左边的最小交换次数。因为累加时交换彼此独立,预处理时可以看成一个队列里只有 iijj 两种数字,那么每个 i 需要的交换次数就是这个 i 之前 j 的个数。
则转移方程是:

F(mask+2j)=F(mask)+kmaskcnt(j,k)F(mask+2^j)=F(mask)+\sum_{k\in mask} cnt(j,k)

实际上时间是 Θ(2202020)\Theta (2^{20} \ast 20\ast 20),估算最多到 41084\ast 10^8 的亚子,还好时限是 4s……

Code

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}

const int maxn=400005,maxc=22;
const int maxs=1048576+10;
const int INF=0x3f3f3f3f3f3f3f3f;

int n,c=20,s=1<<20;
int a[maxn];
int cnt[maxc][maxc];
int f[maxs];
vector<int> vec[maxc];

signed main(){
    n=read();
    for (int i=1;i<=n;i++) a[i]=read()-1,vec[a[i]].push_back(i);

    for (int i=0;i<c;i++) if (vec[i].size())
        for (int j=0;j<c;j++) if (vec[j].size() && i!=j){
            int t=0;
            for (int k=0;k<vec[i].size();k++){
                while ((vec[j][t]<vec[i][k]) && (t+1<vec[j].size())) t++;
                cnt[i][j]+=t+(vec[j][t]<vec[i][k]);
            }
        }
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    for (int i=0;i<s;i++) if (f[i]!=INF){
        for (int j=0;j<c;j++) if ((i&(1<<j))==0){
            int nxt=i|(1<<j);
            int sum=0;
            for (int k=0;k<c;k++) if (i&(1<<k)) sum+=cnt[j][k];
            f[nxt]=min(f[nxt],f[i]+sum);
        }
    }
    printf("%lldn",f[s-1]);
    return 0;
}

好像很长时间没写正经博客了来着……


暂无评论


发表新的评论

所有评论都将经过博主审核。请勿填写无意义邮箱或发表无关评论、广告等,否则会被视为垃圾评论。

提交评论即表明你同意本网站使用 Cookie,并允许本站在后台记录你的邮箱、IP 地址等必要信息。这些信息不会被透露给其他用户。(提交一次评论后,本提示将不再展示)