[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(); }
__________________________________________________________________________________________
本文来自博客园,作者:CuFeO4,转载请注明原文链接:https://www.cnblogs.com/hzoi-Cu/p/18431681
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 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)