1 条题解
-
0
自动搬运
来自洛谷,原作者为

concert_B
情绪困雨天搬运于
2025-08-24 23:15:55,当前版本为作者最后更新于2025-07-17 16:23:08,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P12485 [集训队互测 2024] PM 大师题解
tip
date:2025/7/17,DAY11
本题是模拟赛 T4,Clonoth 也是直接把他集训队的题搬来了,不过神奇的是题解就一篇,提交人数达惊人的 个入。
本人迫不得已只能把官方题解改了一下搬来了。思路不是我的,希望 Clonoth 不会发现。
本题其实难在思考过程,如果真弄懂了代码其实不难,树状数组和线段树都比较板(特指黑题),
但真的好难理解啊啊啊。存好大脑,开始了。以下认为 同阶。
做法
考虑 地由 生成出 。
按照 遍历数组 ,同时维护 表示 是否已经在 中出现过。当 时, ;当 时, 初始设置为上一个 位置的 ,然后使 直到 。
时间复杂度 ,可以通过子任务 。
code
蒟蒻的考场代码。
while(q--){ cin>>x>>k>>y; a[x]=k; if(a[y]!=0){ cout<<a[y]<<'\n'; continue; } for(int j=1;j<=n;j++) fa[j]=j; for(int j=1;j<=n;j++){ if(a[j]==-1) continue; if(a[j]==0){ int b=find(1); if(j==y){ cout<<b<<'\n'; break; } fa[find(b)]=find(b+1); continue; } fa[find(a[j])]=find(a[j]+1); } }并查集的作用就是
vis,已经出现过的和 合并,暴力的比 优美一点悲。做法
考虑解决子任务 。
注意到存在集合 满足对于 中的第 个 ,满足 等于 中的第 小的数。
此时 等价于不存在 。
使用树状数组或平衡树等数据结构维护插入、删除、求 小值即可。 时间复杂度 ,可以通过子任务 。
code
来自 HZ 巨佬的代码 orz:
void add(int x,int k){ for(int i=x;i<=n;i+=i&-i) tr[i]+=k; return; } int qry(int x) { int res=0; while(x){ res+=tr[x]; x-=x&-x; } return res; } int K=n+1; for(int i=1;i<=n;i++) if(a[i]==0){ K=i; break; } for(int i=1;i<=q;i++){ if(a[x[i]]!=-1){ cnt[a[x[i]]]--; if(!cnt[a[x[i]]]) add(a[x[i]],-1); } if(k[i]!=-1){ if(!cnt[k[i]]) add(k[i],1); cnt[k[i]]++; } a[x[i]]=k[i]; if(y[i]<K) cout<<a[y[i]]<<'\n'; else{ int l=1,r=n; while(l<r){ int mid=(l+r)>>1; if (mid-qry(mid)<y[i]-K+1) l=mid+1; else r=mid; } cout<<l<<'\n'; } }做法
考虑解决子任务 。 首先此时原问题等价于在初始全为 的序列 中任意插入正数。
不难注意到,满足做法 中所述性质的集合 在一般的情况下也存在,这启发我们动态的维护 而不是数组 。
以下我们假设 中的正数互不相同,若存在相同仅保留第一个即可(标程的堆中
update操作的意义)。由此,定义 表示 出现的位置。在任何时刻,对于 , 等价于 之前最近的 的 。因此,只要维护了集合 ,在一次 的修改操作中,可以轻易得到是否需要将 从 中删除。
但在一次修改操作中,可能会引起需要往 中加入一些数(由将 从 中删除所引起的 的改变)。对于 定义 ,其中 表示 数组中 之前有多少个 。容易发现 时 ;而一旦出现 则需要将 加入到 中。
那么每次修改对 的修改即为从 开始的一段前缀减 ,直到出现 为止。 使用线段树维护 ,时间复杂度 ,可以通过子任务 。
tip
文中 可以用堆维护, 可以用线段树维护,维护插入、删除、求 小值与做法 类似,标程用树状数组。
code
inline void insert_check(int x){ int k=a[x]; if(pos[k].top()!=x) return; int u=b.kth(pre[x]); if(u<k){ if(!in[k]){ if(k<n){ int i=f.find(1,1,n,k+1,n); if(i){ f.modify(1,1,n,i,inf); g.modify(1,1,n,i,0); b.add(i,1); in[i]=false; ++X; } else i=n+1; if(i<=n) Sum+=i-k; if(k+1<i) g.update(1,1,n,k+1,i-1,1); } b.add(k,-1); in[k]=1; } f.modify(1,1,n,k,b.ask(k)-pre[x]); g.modify(1,1,n,k,inf); } else{ g.modify(1,1,n,k,pre[x]-b.ask(k)); f.modify(1,1,n,k,inf); } return; } inline void insert(int x,int k){ a[x]=k; if(k==-1) return; pos[k].insert(x); insert_check(x); return; }做法
首先原问题等价于在初始全为 的序列 中任意插入或删除正的数。
注意到在做法 中,每次操作对 的改变都是 的,这启发我们使用类似的方式处理删除操作。
对称的,对于 定义 。此时也有 时 ;而一旦出现 则需要将 从 中删除。
修改的形式也是类似的。但值得注意的是,在对 (或 )执行从 开始的一段前缀减 操作时,也需要在 (或 )中对这一段区间进行加 的操作。
使用线段树维护 ,时间复杂度 ,可以通过所有子任务。
code
inline void erase(int x){ int k=a[x]; if(k==-1) return; pos[k].erase(x); if(!pos[k].empty()&&pos[k].top()<x) return; if(in[k]){ if(k<n){ int i=g.find(1,1,n,k+1,n); if(i){ f.modify(1,1,n,i,0); g.modify(1,1,n,i,inf); b.add(i,-1); in[i]=true; ++Y; } else i=n+1; if(i<=n) Sum+=i-k; if(k+1<i) f.update(1,1,n,k+1,i-1,1); } in[k]=false; b.add(k,1); } f.modify(1,1,n,k,inf); g.modify(1,1,n,k,inf); if(!pos[k].empty()) insert_check(pos[k].top()); return; }重构过的代码
封装好了,有需自取。
#include<bits/stdc++.h> using namespace std; const int N=1e6+10,inf=1e9; int n,m; int a[N],pre[N]; bool in[N]; struct heap{ priority_queue<int,vector<int>,greater<int>> p,q; inline void insert(int x){ p.push(x); return; } inline void erase(int x){ q.push(x); return; } inline void update(){ while(!p.empty()&&!q.empty()&&p.top()==q.top()) p.pop(),q.pop(); return; } inline bool empty(){ update(); return p.empty(); } inline int top(){ update(); return p.top(); } inline void clear(){ while(!p.empty()) p.pop(); while(!q.empty()) q.pop(); return; } }pos[N]; struct BIT{ int bit[N]; inline int lowbit(int x){ return x&-x; } inline void add(int x,int k){ for(int i=x;i<=n;i+=lowbit(i)) bit[i]+=k; return; } inline int ask(int x){ int ans=0; for(int i=x;i;i-=lowbit(i)) ans+=bit[i]; return ans; } inline int kth(int k){ if(!k) return 0; int i=0,s=0; for(int w=log2(n);w!=-1;--w){ int j=i+(1<<w); if(j<=n&&s+bit[j]<k) s+=bit[j],i=j; } return i+1; } }b; struct segment_tree{ static const int Size=(1<<21)+10; int sum[Size],tag[Size]; inline void push_up(int p){ sum[p]=min(sum[p<<1],sum[p<<1|1]); return; } inline void push_down(int p){ if(tag[p]){ sum[p<<1]+=tag[p]; sum[p<<1|1]+=tag[p]; tag[p<<1]+=tag[p]; tag[p<<1|1]+=tag[p]; tag[p]=0; } return; } int find(int p,int l,int r,int x,int y){ if(r<x||l>y) return 0; if(x<=l&&r<=y&&sum[p]){ sum[p]--; tag[p]--; return 0; } if(l==r) return l; push_down(p); int mid=l+r>>1; int ans=0; ans+=find(p<<1,l,mid,x,y); if(ans) return ans; ans+=find(p<<1|1,mid+1,r,x,y); return ans; } void update(int p,int l,int r,int x,int y,int k){ if(l>y||r<x) return; if(x<=l&&r<=y){ sum[p]+=k; tag[p]+=k; return; } push_down(p); int mid=l+r>>1; update(p<<1,l,mid,x,y,k); update(p<<1|1,mid+1,r,x,y,k); push_up(p); return; } void modify(int p,int l,int r,int x,int k){ if(r<x||l>x) return; if(l==r){ sum[p]=k; return; } push_down(p); int mid=l+r>>1; modify(p<<1,l,mid,x,k); modify(p<<1|1,mid+1,r,x,k); push_up(p); return; } void build(int p,int l,int r){ tag[p]=0; if(l==r){ sum[p]=inf; return; } int mid=l+r>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); push_up(p); return; } }f,g; int X,Y; long long Sum=0; inline void insert_check(int x){ int k=a[x]; if(pos[k].top()!=x) return; int u=b.kth(pre[x]); if(u<k){ if(!in[k]){ if(k<n){ int i=f.find(1,1,n,k+1,n); if(i){ f.modify(1,1,n,i,inf); g.modify(1,1,n,i,0); b.add(i,1); in[i]=0; ++X; } else i=n+1; if(i<=n) Sum+=i-k; if(k+1<i) g.update(1,1,n,k+1,i-1,1); } b.add(k,-1); in[k]=1; } f.modify(1,1,n,k,b.ask(k)-pre[x]); g.modify(1,1,n,k,inf); } else{ g.modify(1,1,n,k,pre[x]-b.ask(k)); f.modify(1,1,n,k,inf); } return; } inline void insert(int x,int k){ a[x]=k; if(k==-1) return; pos[k].insert(x); insert_check(x); return; } inline void erase(int x){ int k=a[x]; if(k==-1) return; pos[k].erase(x); if(!pos[k].empty()&&pos[k].top()<x) return; if(in[k]){ if(k<n){ int i=g.find(1,1,n,k+1,n); if(i){ f.modify(1,1,n,i,0); g.modify(1,1,n,i,inf); b.add(i,-1); in[i]=1; ++Y; } else i=n+1; if(i<=n) Sum+=i-k; if(k+1<i) f.update(1,1,n,k+1,i-1,1); } in[k]=0; b.add(k,1); } f.modify(1,1,n,k,inf); g.modify(1,1,n,k,inf); if(!pos[k].empty()) insert_check(pos[k].top()); return; } signed main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin>>n>>m; for(int i=1;i<=n;++i){ cin>>a[i]; pre[i]=pre[i-1]+!a[i]; } f.build(1,1,n); g.build(1,1,n); for(int i=1;i<=n;++i) b.add(i,1); int x,k,y; while(m--){ cin>>x>>k>>y; erase(x); insert(x,k); if(a[y]!=0) cout<<a[y]<<'\n'; if(a[y]==0) cout<<b.kth(pre[y])<<'\n'; } #define _ 0 return ~~(0^_^0); }完结散花!!!
- 1
信息
- ID
- 12271
- 时间
- 8000ms
- 内存
- 512MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者