CF1481E Sorting Books

感觉题面很elegant。

我们只需要让颜色相同的组成一个色块,这些色块之间的顺序没有要求,因此,我们可以枚举色块的排列,然后计算到目标序列的最小移动次数。

抽一本书插入队尾,这个操作挺破坏位置连续性的。

抽一本书插入队尾,有两个效果,原先被书阻挡的两个书本变为相邻,连上了,而被抽出的书本身插入队尾,与上一个队尾相邻。

考虑某种颜色的所有数字,如果一开始就是连续块,似乎没有破坏的必要,不一定,如果是两个相邻红色块,这个色块左右两边都是长度为100的蓝色块,那么把这两个红色抽出插入队尾是最好办法,因此还是破坏了这个色块。

如果进一步的讨论要依赖色块左右两个色块的相对数量来划分情况的话,挺烦人的,分支树也不见得会收敛。

能够有限步完结的讨论,恐怕就是要从一些性质入手。

我们考虑分立的同色数字汇合的位置?

我们转换一下视角,我们不需要把抽出一本书后的间隙在想象之中靠拢,这样可以区分哪些书本在原来的位置上,哪些是被移动过的。

我们知道,不可能所有书本都被移动过,因为最多n步,把所有书本都抽出来一定可以重排成你想要的任何结果。

我们要让在原来位置上的书本数量尽可能多。

另外,是否有可能一个书本被抽出过两次或者以上?感觉不太可能,因为抽出一次就可以任意安排在最终序列中的位置(只要挑选好正确的时机的话),抽两次没意义。

 

这样抽出后插入队尾的数字的序号就>n

可以从最终数组区分出两种色块,一种是含有序号>n的色块,一种是不含的。

这样后者一定由原先分立的同色数字之间的阻隔被抽出而形成。

前者则是因为移动人为形成(至少后缀部分是人为的)的聚落。

对于每种颜色,我们可以标记0或者1来表示第一类色块或者第二类色块。

如果初始数组存在 a b a 这样的形式的话,那么颜色a和颜色b都不能同时是第二类色块。

否则,意味最终数组中依然存在着a b a的形式(它们都没被抽出),同色不成块,不行。

这种关系可以连边,如果存在a b a的形式,就给节点a与节点b之间连边。

这样,我们就必须给图染色,使得每条边至少有一个端点被染色,这其实是顶点覆盖问题。

我们可以给每个点赋予权重,等于同色数量,这样我们把该色块标记为第二类,就付出该权重的代价。

这个想法有问题,首先,图的边数可以达到O(N^2),其次,即使标记为第二类点,也不意味着把该颜色的点全部抽出,

有可能是部分抽出,比如该颜色有一个部分是原数组的一个后缀,就不需要抽出,不过可以给这种情况特别修改对应的权重。

我也不清楚最小权覆盖的算法。

 

这个分类标记的过程是否可以给DP做?变成01背包问题。

从队尾往队头扫,如果队尾的同色数字中的最左边的数字,记为L,队尾记为R,如果队尾颜色被标记为第二类,那么[L,R]区间中所有异色的数字都得抽出,它们对应的颜色都被标记为第一类数字。

我们暂且不需要安排抽出后插入队尾的顺序。继续往L的左边看,注意这时被标记为第一类的颜色的位置可以忽略了,因为它们会被抽出。

似乎子问题的结构渐渐清晰了起来。

 

讨论这样一个问题:在初始数组末尾有一个挡板,插入队尾的元素都在挡板之后,因此不能算成与之前的数字相连。

这种情况下,如何调整色块?

其实这样问题反而被简化。

状态里首先要确定区间,其次要知道该区间中哪些颜色被删除了。

所以,f[i]表示i+1~n中出现过的颜色都在1~i数组中被删除,剩下的数组调整成目标色块的最小步数。

这样如果我们把第i个位置的颜色分为第一类,就把i指针跳到这个颜色后缀的开头的前一个位置,否则就把指针跳到这个颜色的最左端的前一个位置。

但是——i位置本身可能在之前被删除了,状态只有一个i,信息不足以区分这一点。可以区分的,只要写一个check函数,判断第x个位置的颜色是否在x+1~n中出现过。

如果i位置的颜色是被删除的,那么就把i指针左移。

 

这个逻辑写出来的代码竟然或者也是理所当然的(但是说不清为什么),WA了。

通过对最后一种颜色的分类讨论,形成挡板,使得插入队尾可能造成的色块连接的可能性被排除,简化了复杂性。

 

意识到了是计算第二类情况的代价出现了问题,里面有的异色数字可能之前就被抽出了,被重复计算。

可以用树状数组来解决这个问题。

 

还是WA,走了。

 

关键是第一次把最后颜色的数组标记为第一类还是第二类从而形成分隔板这一步出了问题。

标记为第二类,不意味着只能把最后一个同色连续块保留,同色其余数字抽出。

比如6 3 666 2 6 如果把6标记为第二类,也可以把第一个6和2抽出。

 

所以还是应该枚举最终形成的最后颜色的色块的第一个位置,利用计算好的f数组来计算对应的答案。

这样反而还统一了之前分开讨论的两种情况。

 

第三个点过了,又倒在了第五个点,吐血。

感觉最后一个数字始终在第n个位置是一种偏见。

 

改了,还好依然是利用数组f来计算的。

AC,老泪纵横。

 

#include <bits/stdc++.h>
using namespace std;
#define FOR(i,n) for (int i=1;i<=n;i++)
#define REP(i,a,b) for (int i=a;i<=b;i++)
 
#define pb push_back
#define fi first
#define se second
#define pi pair<int,int>
#define mp make_pair
 
typedef long long ll;
typedef complex<double> comp;
 
const int inf=0x3f3f3f3f;
const ll linf=1e18;
const int N=5e5+10;
const double eps=1e-10;
const ll mo=1e9+7;
 
int nn,n;
int a[N],b[N],c[N];
int l[N],r[N],p[N],w[N];
int f[N];
bool vis[N];
vector<int> v,h[N];
bool pos[N];
int ans=inf;
int s[N];
int lowbit(int x) {
    return x&-x;
}
void add(int x,int v) {
    while (x<=n) {
        s[x]+=v;
        x+=lowbit(x);
    }
}
int sum(int x) {
    int res=0;
    while (x>=1) {
        res+=s[x];
        x-=lowbit(x);
    }
    return res;
}
bool check(int x) {
    return r[a[x]]==x;
}
int solve() {
    memset(w,0,sizeof(w));
    memset(l,0x3f,sizeof(l));
    memset(r,0,sizeof(r));
    memset(c,0,sizeof(c));
    memset(s,0,sizeof(s));
    memset(vis,0,sizeof(vis));
    memset(pos,0,sizeof(pos));
    FOR(i,n) {
        c[a[i]]++;
        l[a[i]]=min(l[a[i]],i);
        r[a[i]]=max(r[a[i]],i);
        if (i==1) p[i]=1;
        else {
            if (a[i-1]==a[i]) p[i]=p[i-1];
            else p[i]=i;
        }
    }
    FOR(i,n) pos[r[a[i]]]=1;
    for (int i=n;i>=1;i--) {
        if (vis[a[i]]) {
            w[i]=w[i+1];
        } else {
            w[i]=w[i+1]+c[a[i]];
            vis[a[i]]=1;
        }
    }
    FOR(i,n) f[i]=inf;
    FOR(i,n) {
        if (pos[i]) add(i,c[a[i]]);
        if (check(i)) {
            f[i]=min(f[i],f[p[i]-1]+c[a[i]]);
            f[i]=min(f[i],f[l[a[i]]-1]+sum(i)-sum(l[a[i]]-1)-c[a[i]]);
        } else {
            f[i]=f[i-1];
        }
    }
    return f[n];
}
int main() {
 
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
 
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    cin>>n;
    FOR(i,n) cin>>a[i];
    solve();
    FOR(i,n) h[a[i]].pb(i);
    FOR(i,n) if (r[i]) {
        int cnt=0;
        for (auto &j:h[i]) {
            ans=min(ans,f[j-1]+cnt+w[j]-c[i]);
            cnt++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

 

posted @ 2021-03-06 16:33  AngelKnows  阅读(76)  评论(0)    收藏  举报