[Ynoi2016] 镜中的昆虫

镜中的昆虫

简要题意 : 区间赋值,查询区间中不同颜色个数。

只有区间赋值的话考虑珂朵莉树,但是显然数据不是随机的,所以需要转化一下。

\(pre_i\)表示上一个与\(a_i\)相同的位置,没有的话记为0。

对于每次查询\(ans = \sum_{i=l}^r[pre_i< l]\)

那么就考虑如何更改\(pre\),暴力吗?显然不行。

发现每次都是将几个颜色段更改成一个颜色段,那么对于一次区间赋值,\(pre\)值会更改的位置只有每段的起始位置,其他位置不变,所以就可以将\(pre\)的区间更改转化为单点更改。

但是这样的话只能暴力跑了,复杂度呢?

然后就引出一个区间赋值的重要结论 :

  • 对一个长度为\(n\)的数组进行\(m\)次区间赋值,\(pre\)数组的改变次数为\(O(n+m)\)

证明 :

考虑珂朵莉树区间赋值的过程,先将两端的颜色段分开,再将中间的颜色段换为同一个颜色段。

所以每次更改就是删除若干个颜色段,再至多加入3个颜色段,发现\(pre\)修改的次数与被删除的颜色段个数同阶,而且每个颜色段至多被删去一次,所以添加的颜色段是\(O(m)\),初始颜色段是\(O(n)\)的,所以\(pre\)修改的次数是\(O(n+m)\)的。

然后再给每一次更改加上一个时间维度就可以了。

容易发现上面统计的答案就是二维数点,加上时间戳就变成了三维偏序,直接\(cdq+树状数组\)即可。

时间复杂度\(O(n\log^2 n)\)

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define infile(x) freopen(x,"r",stdin)
#define outfile(x) freopen(x,"w",stdout)
#define errfile(x) freopen(x,"w",stderr)
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#ifdef LOCAL
FILE *InFile = infile("in.in"),*OutFile = outfile("out.out");
// FILE *ErrFile=errfile("err.err");
#else
FILE *Infile = stdin,*OutFile = stdout;
//FILE *ErrFile = stderr;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10;
int n,m,a[N],lst[N<<1],cnt,pre[N<<1],tot,Qtot,ans[N];
map<int,int> mp;
#define eb emplace_back
#define All(x) x.begin(),x.end()
struct Query{int x,y,t,val,id;}q[N<<4];
class ODT{
public:
struct node{
int l,r,val;
inline bool operator < (node x) const {return l < x.l;}
node(int L,int R = 0,int Val = 0):l(L),r(R),val(Val){};
};
set<node> s,col[N<<1];
#define sit set<node>::iterator
inline sit Insert(int l,int r,int val){
col[val].insert(node(l,r,val));
return s.insert(node(l,r,val)).first;
}
inline void Delete(int l,int r,int val){
col[val].erase(node(l,r,val));
s.erase(node(l,r,val));
}
inline int get_pre(int p){
sit it = --s.upper_bound(node(p));
if(it->l < p) return p-1;
else{
sit res = col[it->val].lower_bound(node(p));
if(res != col[it->val].begin()) return (--res)->r;
return 0;
}
}
inline sit split(int p){
sit it = s.lower_bound(node(p));
if(it != s.end() && it->l == p) return it;
it--;
int l = it->l,r = it->r,val = it->val;
Delete(l,r,val);
Insert(l,p-1,val);
return Insert(p,r,val);
}
inline void assign(int l,int r,int val,int t){
sit it2 = split(r + 1),it1 = split(l);
vector<int> res;
for(sit it = it1;it != it2; ++it){
if(it != it1) res.eb(it->l);
sit nxt = col[it->val].upper_bound(*it);
if(nxt != col[it->val].end()) res.eb(nxt->l);
col[it->val].erase(*it);
}
s.erase(it1,it2);Insert(l,r,val);res.eb(l);
sit nxt = col[val].upper_bound(node(l));
if(nxt != col[val].end()) res.eb(nxt->l);
for(auto i:res){
q[++cnt] = {i,pre[i],t,-1,0};
pre[i] = get_pre(i);
q[++cnt] = {i,pre[i],t,1,0};
}
}
}odt;
struct BIT{
private:int tree[N];inline int lowbit(int x){return (x&(-x));}
public:int mx;
inline void update(int pos,int val){rep(i,pos,mx,lowbit(i)) tree[i] += val;}
inline int query(int pos){int res = 0;drep(i,pos,1,lowbit(i)) res += tree[i];return res;}
}t;
inline bool cmp(const Query &x,const Query &y){return (x.x!=y.x?x.x<y.x:x.id<y.id);}
inline bool cmp1(const Query &x,const Query &y){return (x.t!=y.t?x.t<y.t:x.id<y.id);}
void cdq(int l,int r){
if(l == r) return;
int mid = (l + r) >> 1;
cdq(l,mid);cdq(mid+1,r);
int i = l,j = mid + 1;
while(j <= r){
while(i <= mid && q[i].x <= q[j].x){
if(!q[i].id) t.update(q[i].y+1,q[i].val);
i++;
}
if(q[j].id) ans[q[j].id] += t.query(q[j].y + 1) * q[j].val;
j++;
}
rep(k,l,i-1,1) if(!q[k].id) t.update(q[k].y+1,-q[k].val);
inplace_merge(q+l,q+mid+1,q+r+1,cmp);
}
inline void solve(){
cin>>n>>m;t.mx = n+1;
rep(i,1,n,1){
cin>>a[i];
if(!mp[a[i]]) mp[a[i]] = ++tot;
a[i] = mp[a[i]],pre[i] = lst[a[i]],lst[a[i]] = i;
q[++cnt] = {i,pre[i],0,1,0};
odt.Insert(i,i,a[i]);
}
// exit(0);
rep(i,1,m,1){
int op;cin>>op;
if(op == 1){
int l,r,v;cin>>l>>r>>v;
if(!mp[v]) mp[v] = ++tot;
v = mp[v];
odt.assign(l,r,v,i);
}
else{
int l,r;cin>>l>>r;Qtot++;
q[++cnt] = {r,l-1,i,1,Qtot};
q[++cnt] = {l-1,l-1,i,-1,Qtot};
}
}
// cerr<<Qtot<<'\n';
sort(q+1,q+1+cnt,cmp1);
// cerr<<cnt<<'\n';
cdq(1,cnt);
// for(int i = 1;i <= cnt; ++i){
// cout<<q[i].x<<' '<<q[i].y<<' '<<q[i].t<<' '<<q[i].val<<' '<<q[i].id<<'\n';
// }
// exit(0);
rep(i,1,Qtot,1) cout<<ans[i]<<'\n';
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
cout.tie(nullptr)->sync_with_stdio(false);
solve();
}
posted @   CuFeO4  阅读(13)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示