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;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/18703198
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步