感觉及其好玩的题目

· · 题解

第一眼看上去以为是神仙分块题/根号分治,没想到 \mathcal{O}(n) 或者 \mathcal{O}(n\log n) 就轻松解决了。

听说 hohodef 写了个类春节十二响的启发式合并还是啥,然后常数被我吊起来打了。

一开始以为是什么奇怪玄学复杂度,最后发现是 \mathcal{O}(n\log n)

事实上可以稍作调整做到 \mathcal{O}(n) 但是比较懒(

做法

大概就是说,找到每一个块的块头,然后和一个虚拟点建立“父子关系”,这些就是下次要删掉的点。同时用链表维护一下在序列中的前后继,以及这些点删的顺序。

删的话很方便,如果一个点没有儿子了,那么直接删掉就可以了。

如果这个点还有儿子那么把儿子接到下一次要删的点上。

然后这两个过程中都需要同时在链表上进行修改。

当你把一个块删完的时候判一下前后两个块颜色是不是一样,如果一样那么直接把后面那个块块头连到前面那个块块尾上面去。

只需要链表操作,速度快的一批(

所以为啥只有 n=200000 啊。

代码

#include<bits/stdc++.h>
#define N 200009
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0',c=getchar();}
    return x*f;
}
ll n,a[N],son[N],nxt[N],pre[N],Son[N],cnt,p[N],tot=1;
void init(){
    n=read();
    a[0]=a[n+1]=-1;
    for(int i=1;i<=n;i++) a[i]=read(),nxt[i]=i+1,pre[i]=i-1;
    for(int i=1;i<=n;i++)
        if(a[i]==a[i-1]) son[i-1]=i;
        else Son[++cnt]=i;
}
void get(){
    while(tot){
        tot=0;
        for(int i=1;i<=cnt;i++){
            if(Son[i]==0) continue;
            p[++tot]=Son[i];
            nxt[pre[Son[i]]]=nxt[Son[i]];
            pre[nxt[Son[i]]]=pre[Son[i]];
            if(son[Son[i]]!=0) Son[i]=son[Son[i]];
            else Son[i]=0;
        }
        for(int i=1;i<=cnt;i++)
            if(Son[i]==0) swap(Son[i],Son[cnt]),cnt--;
        for(int i=1;i<=cnt;i++){
            if(Son[i]==0) continue;
            if(a[pre[Son[i]]]==a[Son[i]]) son[pre[Son[i]]]=Son[i],Son[i]=0;
        }
        if(tot==0) break;
        sort(p+1,p+tot+1);
        for(int i=1;i<tot;i++) printf("%lld ",p[i]);
        printf("%lld\n",p[tot]);
    }
}
int main(){
    //freopen("fruit.in","r",stdin);
    //freopen("fruit.out","w",stdout);
    init();
    get();
    return 0;
}

复杂度分析

复杂度分析一下,令 \sum\text{块数}=k,复杂度是 \mathcal{O}(k\log k)(有一个排序),然后我们发现 \sum\text{块数}=n,所以复杂度是 \mathcal{O}(n\log n)