潜龙未见静水流,沉默深藏待时秋。一朝破空声势振,惊世骇俗展雄猷。
随笔 - 82, 文章 - 0, 评论 - 3, 阅读 - 2211

P4690 [Ynoi2016] 镜子里的昆虫 题解

目录

题目描述

给定长为 \(n\) 的序列 \(a\)\(m\) 次操作:

  • 1 l r x :将 \(a_l,\cdots,a_r\) 修改为 \(x\)
  • 2 l r :询问 \(a_l,\cdots,a_r\) 中不同的数的个数。

数据范围

  • \(1\le n,m\le 10^5\)
  • \(1\le a_i,x\le 10^9\)

时间限制 \(\texttt{1.5s}\) ,空间限制 \(\texttt{64MB}\)

分析

强烈建议改回原名——镜中的昆虫。

区间数颜色常见的套路:维护 pre 、莫队、 bitset

\(\texttt{Key observation}\) :关于 pre 的修改只有 \(\mathcal O(n+m)\) 次。

证明:

考虑颜色段均摊,总共只有 \(n+m\) 个颜色段,每个颜色段被删除时会带来 \(2\) 次修改。

加入一个颜色段也会带来 \(2\) 次修改,算上分裂的两个颜色段,总共 \(6\) 次修改。

询问即求 \(\sum_{i=l}^r[pre_i\lt l]\) ,本质是二维偏序。

将修改看成时间维度,三维偏序用 \(\texttt{cdq}\) 分治解决即可,时间复杂度 \(\mathcal O((n+m)\log^2(n+m))\)

总点数应该开几倍空间?

由于每次修改需要新开两个点(删除旧贡献、加入新贡献),根据上面的证明,总点数不超过 \(n+2(2(n+m)+6m)\) ,即 \(21\) 倍空间可以确保没有问题。

但是并不是所有操作都是修改操作(查询操作只需新开一个点),并且不是每次修改都有效,实测开 \(10\) 倍能过。

当然博主的实现仍然有优化空间(有一些无效修改没判),据说有开 \(8\) 倍空间过的,卡空间的事情留给神通广大的读者。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=1e5+5,inf=1e9;
int l,m,n,r,t=1,x,op,tot;
int c[maxn],pre[maxn],res[maxn];
struct seg
{
    int l,r,v;
};
bool operator<(seg a,seg b)
{
    return a.l!=b.l?a.l<b.l:a.r<b.r;
}
set<seg> all;
map<int,set<pii>> s;
struct poi
{///v=±1 权值为v,v=0 询问
    int a,b,c,v;
}p[10*maxn];
bool operator<(poi x,poi y)
{
    if(x.a!=y.a) return x.a<y.a;
    if(x.b!=y.b) return x.b<y.b;
    if(x.c!=y.c) return x.c<y.c;
    return !x.v<!y.v;
}
int get(int x)
{///求pre[x]
    auto [l,r,v]=*--all.lower_bound({x,inf,0});
    if(l!=x) return x-1;
    auto it=s[v].find(mp(l,r));
    return it!=s[v].begin()?(*--it).se:0;
}
void modify(int x,int v=-1)
{
    if(v==-1) v=get(x);
    if(v!=pre[x]) p[++tot]={x,pre[x],t,-1},p[++tot]={x,pre[x]=v,t,1};
}
void split(int x)
{
    auto [l,r,v]=*--all.lower_bound({x,inf,0});
    if(l==x) return ;
    all.erase({l,r,v}),s[v].erase(mp(l,r));
    all.insert({l,x-1,v}),all.insert({x,r,v}),s[v].insert(mp(l,x-1)),s[v].insert(mp(x,r));
}
void add(int x,int v)
{
    if(v) while(x<=t) c[x]+=v,x+=x&-x;
}
int query(int x)
{
    int res=0;
    while(x) res+=c[x],x-=x&-x;
    return res;
}
void cdq(int l,int r)
{
    if(l==r) return ;
    int i=l,mid=(l+r)>>1;
    cdq(l,mid),cdq(mid+1,r);
    sort(p+l,p+mid+1,[&](poi x,poi y){return x.b<y.b;});
    sort(p+mid+1,p+r+1,[&](poi x,poi y){return x.b<y.b;});
    for(int j=mid+1;j<=r;j++)
    {
        while(i<=mid&&p[i].b<=p[j].b) add(p[i].c,p[i].v),i++;
        if(!p[j].v) res[p[j].c]+=query(p[j].c);
    }
    while(--i>=l) add(p[i].c,-p[i].v);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x),all.insert({i,i,x}),s[x].insert(mp(i,i));
        p[++tot]={i,pre[i]=get(i),t,1};
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&op,&l,&r);
        if(op==1)
        {
            scanf("%d",&x),split(l),split(r+1);
            for(int L=l,R=0,v=0;L<=r;L=R+1)
            {
                auto it1=all.lower_bound({L,0,0});
                R=(*it1).r,v=(*it1).v,all.erase(it1);
                auto it2=s[v].erase(s[v].find(mp(L,R)));
                if(L!=l) modify(L,L-1);
                if(it2!=s[v].end()) modify((*it2).fi);
            }
            all.insert({l,r,x});
            auto it=++s[x].insert(mp(l,r)).fi;
            modify(l);
            if(it!=s[x].end()) modify((*it).fi);
        }
        else res[t]-=l-1,p[++tot]={r,l-1,t++,0};
        ///(i,pre_i,*)对(r,l-1,*)偏序,注意i<=l-1时一定满足,所以答案要减l-1
    }
    sort(p+1,p+tot+1);
    cdq(1,tot);
    for(int i=1;i<t;i++) printf("%d\n",res[i]);
    return 0;
}

posted on   peiwenjun  阅读(11)  评论(0编辑  收藏  举报

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示