lxl 数据结构杂题选讲
CF793F Julia the snail
考虑扫描线,对于一个 \(l_i,r_i\) ,我们需要的操作就是对左端点在 \([1,l_i]\) 中且大于 \(l_i\) 的答案改为 \(r_i\) ,考虑吉司机线段树的思路,如果区间内只有最大值超过了 \(l_i\) 则暴力修改,否则递归下去,均摊复杂度 \(O(n\log n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
struct node{
int mx1,mx2,lazy;bool le,ri;
node(int _mx1=0,int _mx2=0,bool _le=0,bool _ri=0){
mx1=_mx1;mx2=_mx2;le=_le;ri=_ri;lazy=0;
}
inline node operator +(node b){
node res;
if(mx1>b.mx1)return node(mx1,max(mx2,b.mx1),1,0);
if(mx1<b.mx1)return node(b.mx1,max(b.mx2,mx1),0,1);
return node(mx1,max(mx2,b.mx2),1,1);
}
}tree[400005];
void build(int l=1,int r=n,int i=1){
if(l==r){
tree[i]=node(l,0,0,0);
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
inline void push(int i){
if(tree[i].le)tree[i<<1].mx1=tree[i<<1].lazy=tree[i].lazy;
if(tree[i].ri)tree[i<<1|1].mx1=tree[i<<1|1].lazy=tree[i].lazy;
tree[i].lazy=0;
}
void update(int fr,int to,int lim,int v,int l=1,int r=n,int i=1){
if(fr>r||to<l||tree[i].mx1<lim)return ;
if(fr<=l&&to>=r&&tree[i].mx2<lim){
tree[i].mx1=tree[i].lazy=v;
return ;
}
if(tree[i].lazy)push(i);
int mid=(l+r)>>1;
update(fr,to,lim,v,l,mid,i<<1);update(fr,to,lim,v,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
int query(int loc,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return 0;
if(l==r)return tree[i].mx1;
if(tree[i].lazy)push(i);
int mid=(l+r)>>1;
return query(loc,l,mid,i<<1)+query(loc,mid+1,r,i<<1|1);
}
vector<int> vec[100005];
vector<pair<int,int> > qry[100005];
int ans[100005];
int main(){
scanf("%d",&n);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int l,r;scanf("%d%d",&l,&r);
vec[r].push_back(l);
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int l,r;scanf("%d%d",&l,&r);
qry[r].push_back(make_pair(l,i));
}
build();
for(int i=1;i<=n;i++){
for(auto it:vec[i])update(1,it,it,i);
for(auto it:qry[i]){
ans[it.second]=query(it.first);
}
}
for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
return 0;
}
CF1446D2 Frequency Problem (Hard Version)
可以证明,这两个出现次数最多的元素中,必定有一个是全局的众数。
考虑答案一定是全局删掉一个前缀和一个后缀。
删除的过程中,全局的众数一定一直为众数,直到出现了一个与其出现次数相等的数。
假设众数 \(x\) 出现次数为 \(a\),我们目前考虑一个值 \(y\),计算 \(y\) 与 \(x\) 的答案最大是多少,\(y\) 出现 \(b\) 次。
我们如果得到一个 \(O(a+b)\) 的算法,这个题就是根号题了。
我们要得到一个 \(O(b\times \text{polylog}(n))\) 的算法。
初始将每个 \(x\) 出现的位置标记为无意义的位置。
我们枚举 \(y\) 出现的每个位置,然后找离这些位置最近的 \(x\) 出现的无意义位置(左右两边都找),然后将这些位置标记为有意义的位置。
可以证明标记结束后无意义的位置和答案无关,于是无意义的位置将序列分为多段不相关的区间。
所有有意义的位置,与 \(y\) 出现的位置,这些位置左右 \(1\) 的位置可能是答案端点,所以只有 \(O(b)\) 个可能的答案端点。
考虑为了找出答案端点,我们需要一个数据结构,支持查询前驱与删点。
这个可以使用序列线性并查集来做,对每个 \(y\) 的每个位置,预处理出其前面第一个 \(x\) 出现位置挂上去。
本题还需要支持修改后回退,由于每次修改 \(Ω(\log n)\) 次后才有可能进行一次并查集上的合并,所以复杂度正确。
将 \(x\) 的位置设为 \(1\),\(y\) 的位置设为 \(-1\),对有意义的位置跑一个前缀和,维护出和为 \(1,2,…b\) 的最长与最短前缀,这样就可以找出和为 \(0\) 的最长子段,即答案了。
如果区间中出现次数最多的数不是 \(x,y\) 也没关系,因为答案是取 \(\max\) 的,这样只是这个区间中 \(x,y\) 的贡献不够优。
所有数的出现次数和为 \(n\),故每次的 \(b\) 和是 \(O(n)\) 的,总时间复杂度 \(O(n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,s=500;
int a[200005],tot[200005],pre[400005];
int ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)tot[a[i]]++;
int Tmp=0;
for(int i=1;i<=n;i++)if(tot[i]>tot[Tmp])Tmp=i;
for(int t=1;t<=n;t++){
if(tot[t]<=s||tot[t]==Tmp)continue;
for(int i=0;i<=2*n;i++)pre[i]=0;
int cnt=0;
for(int i=1;i<=n;i++){
if(a[i]==Tmp)cnt++;
else if(a[i]==t)cnt--;
if(pre[cnt+n]||!cnt)ans=max(ans,i-pre[n+cnt]);
else pre[cnt+n]=i;
}
}
for(int t=1;t<=s;t++){
int cnt=0,L=0;
for(int i=1;i<=n;i++)tot[i]=0;
for(int i=1;i<=n;i++){
tot[a[i]]++;
if(tot[a[i]]==t)cnt++;
while(tot[a[i]]>t){
if(tot[a[L+1]]==t)cnt--;
tot[a[++L]]--;
}
if(cnt>=2)ans=max(ans,i-L);
}
}
printf("%d\n",ans);
return 0;
}
CF1340F Nastya and CBS
本来想用矩阵随机化,但互逆矩阵是满足交换律的,处理不了 )(
的情况。
考虑哈希,对于一个区间,消掉可以匹配的括号,剩下的如果是 ...{]...
的形式,那么剩下的部分一定不合法,因此一个可能合法的区间一定存在一个分界点,使得左边只剩右括号,右边只剩左括号。
用结构体维护一个可加可减的哈希,线段树上每个节点维护它左右两边剩余的部分,合并时要将左儿子的左括号和右儿子的右括号相抵消,对于长的那一侧像《楼房重建》那样递归下去查询即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
namespace Hashing{
const int md=1e9+7;
inline int pwr(int x,int y){
int res=1;
while(y){
if(y&1)res=1ll*res*x%md;
x=1ll*x*x%md;y>>=1;
}
return res;
}
const int B=114514,iB=pwr(B,md-2);
int _w[200015],*w=_w+100005;
inline void Init(int N){
w[0]=1;
for(int i=1;i<=N;i++){
w[i]=1ll*w[i-1]*B%md;
w[-i]=1ll*w[-i+1]*iB%md;
}
}
struct str{
int x,m;
str(){x=m=0;}
str(int v){x=v,m=1;}
str(int _x,int _m){x=_x;m=_m;}
inline bool operator ==(str b){
return m==b.m&&x==b.x;
}
inline str operator +(str b){
return str((x+1ll*b.x*w[m])%md,m+b.m);
}
inline str operator -(str b){
return str(1ll*(x-b.x+md)*w[-b.m]%md,m-b.m);
}
};
}
using Hashing::str;
int n,k,q;
struct dat{
bool err;
str vl,vr;
dat(){err=0;}
dat(int x){err=0;x>0?vr=str(x):vl=str(-x);}
}tr[400005];
str GetL(int i,int k){
if(!k)return str();
if(k==tr[i].vl.m)return tr[i].vl;
if(k<=tr[i<<1].vl.m)return GetL(i<<1,k);
return tr[i<<1].vl+(GetL(i<<1|1,k-tr[i<<1].vl.m+tr[i<<1].vr.m)-tr[i<<1].vr);
}
str GetR(int i,int k){
if(!k)return str();
if(k==tr[i].vr.m)return tr[i].vr;
if(k<=tr[i<<1|1].vr.m)return GetR(i<<1|1,k);
return tr[i<<1|1].vr+(GetR(i<<1,k-tr[i<<1|1].vr.m+tr[i<<1|1].vl.m)-tr[i<<1|1].vl);
}
inline void pushup(int i){
if(tr[i<<1].err||tr[i<<1|1].err){
tr[i].err=1;
return ;
}
tr[i].err=0;
tr[i].vl=tr[i<<1].vl;tr[i].vr=tr[i<<1|1].vr;
if(tr[i<<1].vr.m<=tr[i<<1|1].vl.m){
if(tr[i<<1].vr==GetL(i<<1|1,tr[i<<1].vr.m))tr[i].vl=tr[i].vl+(tr[i<<1|1].vl-tr[i<<1].vr);
else tr[i].err=1;
}
else{
if(tr[i<<1|1].vl==GetR(i<<1,tr[i<<1|1].vl.m))tr[i].vr=tr[i].vr+(tr[i<<1].vr-tr[i<<1|1].vl);
else tr[i].err=1;
}
}
void build(int l=1,int r=n,int i=1){
if(l==r){
int x;scanf("%d",&x);
tr[i]=x;
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
pushup(i);
}
void update(int loc,int v,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tr[i]=v;return ;
}
int mid=(l+r)>>1;
update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
pushup(i);
}
int stk[45], tp;
void Extract(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return ;
if(fr<=l&&to>=r){
stk[++tp]=i;return ;
}
int mid=(l+r)>>1;
Extract(fr,to,l,mid,i<<1);Extract(fr,to,mid+1,r,i<<1|1);
}
str seq[45];
str gVal(int i,int k){
if(!k)return str();
if(k==seq[i].m)return seq[i];
if(k<=tr[stk[i]].vr.m)return GetR(stk[i],k);
return tr[stk[i]].vr+(gVal(i-1,k-tr[stk[i]].vr.m+tr[stk[i]].vl.m)-tr[stk[i]].vl);
}
inline bool query(int l,int r){
tp=0;Extract(l,r);
for(int i=1;i<=tp;i++){
if(tr[stk[i]].err)return 0;
if(seq[i-1].m<tr[stk[i]].vl.m)return 0;
if(tr[stk[i]].vl==gVal(i-1,tr[stk[i]].vl.m))seq[i]=tr[stk[i]].vr+(seq[i-1]-tr[stk[i]].vl);
else return 0;
}
return !seq[tp].m;
}
int main(){
scanf("%d%d",&n,&k);
Hashing::Init(n);
build();
scanf("%d",&q);
while(q--){
int op,x,y;scanf("%d%d%d",&op,&x,&y);
if(op==1)update(x,y);
else puts(query(x,y)?"Yes":"No");
}
return 0;
}
P7290 「EZEC-5」暴力出奇迹
考虑一条竖线段 \((i,a)\) — \((i,b)\) 的贡献,它会对所有 \(x\le i\le y\),\(a\le j\le b\) 的横线段 \((x,j)\) — \((y,j)\) 加 \(1\) 。
考虑扫描线,沿着 \(x\) 轴扫描,\(\text{KDT}\) 维护,竖线段 \((i,a)\) — \((i,b)\) 相当于平面矩形加,对于询问的 \(x,y,l,r\) ,在 \(l\) 处将询问 \((x,y)\) 插入 \(\text{KDT}\) 中,在 \(r\) 处删除,并查询这个点从插入到删除过程中的历史最大值。
时间复杂度 \(O(n\sqrt m+m\log m)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,key;
vector<pair<int,int> > vec[2000005];
struct node{
int x[2],lx,rx,ly,ry,id;
node(int _x=0,int _y=0,int _id=0){
x[0]=lx=rx=_x;x[1]=ly=ry=_y;id=_id;
}
inline bool operator <(const node &b)const{
return x[key]<b.x[key];
}
}a[2000005],tree[2000005];
int le[2000005],ri[2000005],cnt,fa[2000005],mp[2000005];
int build(int l=1,int r=m,int _=0,int fi=0){
if(l>r)return 0;
int mid=(l+r)>>1,i=++cnt;key=_;fa[i]=fi;
nth_element(a+l,a+mid,a+r+1);tree[i]=a[mid];mp[a[mid].id]=i;
le[i]=build(l,mid-1,_^1,i);ri[i]=build(mid+1,r,_^1,i);
if(le[i]){
tree[i].lx=min(tree[i].lx,tree[le[i]].lx);tree[i].ly=min(tree[i].ly,tree[le[i]].ly);
tree[i].rx=max(tree[i].rx,tree[le[i]].rx);tree[i].ry=max(tree[i].ry,tree[le[i]].ry);
}
if(ri[i]){
tree[i].lx=min(tree[i].lx,tree[ri[i]].lx);tree[i].ly=min(tree[i].ly,tree[ri[i]].ly);
tree[i].rx=max(tree[i].rx,tree[ri[i]].rx);tree[i].ry=max(tree[i].ry,tree[ri[i]].ry);
}
// cout<<"build "<<" "<<tree[i].id<<" ["<<tree[i].lx<<" "<<tree[i].rx<<"] ["<<tree[i].ly<<" "<<tree[i].ry<<"]"<<endl;
return i;
}
int mx[2000005],hmx[2000005],lazy[2000005],hlazy[2000005];
inline void add(int i,int v,int hv){
hmx[i]=max(hmx[i],mx[i]+hv);hlazy[i]=max(hlazy[i],lazy[i]+hv);
mx[i]+=v;lazy[i]+=v;
}
inline void push(int i){
if(lazy[i]||hlazy[i]){
add(le[i],lazy[i],hlazy[i]);add(ri[i],lazy[i],hlazy[i]);
lazy[i]=hlazy[i]=0;
}
}
void update(int loc,int v,int i=1){
if(!i||tree[i].lx>loc||tree[i].ry<loc)return ;
if(tree[i].rx<=loc&&tree[i].ly>=loc){
// cout<<"add "<<loc<<" "<<v<<" "<<tree[i].id<<" ["<<tree[i].lx<<" "<<tree[i].rx<<"] ["<<tree[i].ly<<" "<<tree[i].ry<<"]"<<endl;
add(i,v,v);return ;
}
if(tree[i].x[0]<=loc&&tree[i].x[1]>=loc){
// cout<<"add "<<loc<<" "<<v<<" "<<tree[i].id<<" ["<<tree[i].x[0]<<"] ["<<tree[i].x[1]<<"]"<<endl;
mx[i]+=v;hmx[i]=max(hmx[i],mx[i]);
}push(i);
update(loc,v,le[i]);
update(loc,v,ri[i]);
}
int stk[2000005];
inline void insert(int id){
int top=0;
for(int i=mp[id];i;i=fa[i])stk[++top]=i;
while(top)push(stk[top--]);
int x=mp[id];hmx[x]=mx[x];
// cout<<"insert "<<id<<" "<<hmx[fa[id].back()]<<endl;
}
inline int query(int id){
int top=0;
for(int i=mp[id];i;i=fa[i])stk[++top]=i;
while(top)push(stk[top--]);
return hmx[mp[id]];
}
vector<int> Be[2000005],Ed[2000005];
int ans[2000005];
inline int read(){
int res=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int a,b;a=read();b=read();
vec[a].push_back(make_pair(i,1));
vec[b+1].push_back(make_pair(i,-1));
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
int l,r,x,y;l=read();r=read();x=read();y=read();
a[i]=node(x,y,i);
Be[l].push_back(i);
Ed[r].push_back(i);
}
build();
for(int i=1;i<=n;i++){
for(auto it:vec[i])update(it.first,it.second);
for(auto it:Be[i])insert(it);
for(auto it:Ed[i])ans[it]=query(it);
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
[Ynoi2008] rdCcot
考虑对每个联通块维护一个代表元素,然后做扫描线。
每个点向深度比它小的点匹配,深度相同时编号小的点优先,容易发现这样一来每个联通块有且仅有一个元素匹配不到点,它就是我们需要的代表元素。
点分治对于每个点 \(x\) 预处理出 \(L_x=\max_{i<x,dist(i,x)\le C}\limits i\) 和 \(R_x=\min_{i>x,dist(i,x)\le C}\limits i\) ,询问 \(l,r\) 查询的就是 \(\sum_{i\in[l,r]}\limits[L_i<l]\cdot[R_i>r]\),扫描线即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,C;
int ver[600005],ne[600005],head[300005],cnt;
inline void link(int x,int y){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;
}
int dep[300005];
void init(int x,int fi){
dep[x]=dep[fi]+1;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
init(u,x);
}
}
int val[300005],tree[300005],le[300005],ri[300005],rk[300005];
inline void build(){
for(int i=1;i<=n;i++)rk[i]=i;
random_shuffle(rk+1,rk+n+1);
}
inline void pushup(int x){
tree[x]=max(val[x],max(tree[le[x]],tree[ri[x]]));
}
int merge(int x,int y){
if(!x||!y)return x|y;
if(rk[x]>rk[y]){
ri[x]=merge(ri[x],y);pushup(x);
return x;
}
le[y]=merge(x,le[y]);pushup(y);
return y;
}
void split(int x,int v,int &a,int &b){
if(!x){a=0;b=0;return ;}
if(x<=v){
split(ri[x],v,ri[x],b);
a=x;
}
else {
split(le[x],v,a,le[x]);
b=x;
}pushup(x);
}
int Rt;
int Getpre(int x,int v){
if(!x)return 0;
if(ri[x]&&tree[ri[x]]>=v)return Getpre(ri[x],v);
else if(val[x]>=v)return x;
return Getpre(le[x],v);
}
int Getnxt(int x,int v){
if(!x)return n+1;
if(le[x]&&tree[le[x]]>=v)return Getnxt(le[x],v);
else if(val[x]>=v)return x;
return Getnxt(ri[x],v);
}
int siz[300005],mxp[300005],rt;
bool vis[300005];
void findrt(int x,int fi,int tot){
siz[x]=1;mxp[x]=0;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(vis[u]||u==fi)continue;
findrt(u,x,tot);siz[x]+=siz[u];
mxp[x]=max(mxp[x],siz[u]);
}
mxp[x]=max(mxp[x],tot-siz[x]);
if(mxp[x]<mxp[rt])rt=x;
}
vector<pair<int,int> > tmp;
int dis[300005];
void dfs(int x,int fi){
tmp.push_back(make_pair(dep[x],x));
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(vis[u]||u==fi)continue;
dis[u]=dis[x]+1;dfs(u,x);
}
}
int L[300005],R[300005];
void solve(int x){
vis[x]=1;tmp.clear();dis[x]=0;dfs(x,x);
sort(tmp.begin(),tmp.end());
for(auto it:tmp){
if(dis[it.second]>C)continue;
int a=0,b=0;split(Rt,it.second,a,b);
L[it.second]=max(L[it.second],Getpre(a,dis[it.second]));
R[it.second]=min(R[it.second],Getnxt(b,dis[it.second]));
tree[it.second]=val[it.second]=C-dis[it.second];le[it.second]=ri[it.second]=0;
Rt=merge(merge(a,it.second),b);
}
Rt=0;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(vis[u])continue;
mxp[rt=0]=n;findrt(u,x,siz[u]);
solve(rt);
}
}
int seg[300005];
inline void add(int x,int v){
while(x<=n+1){
seg[x]+=v;
x+=(x&-x);
}
}
inline int query(int x){
int res=0;
assert(x>=0);
while(x){
res+=seg[x];
x&=(x-1);
}
return res;
}
int ans[600005];
vector<pair<int,int> > vec[300005],qry[300005];
inline int read(){
int res=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
return res;
}
int main(){
scanf("%d%d%d",&n,&m,&C);
for(int i=2;i<=n;i++){
int x;x=read();
link(i,x);link(x,i);
}
init(1,1);
build();
for(int i=1;i<=n;i++)R[i]=n+1;
mxp[rt=0]=n;findrt(1,1,n);solve(rt);
for(int i=1;i<=m;i++){
int l,r;l=read();r=read();
qry[r].push_back(make_pair(l,i));
qry[l-1].push_back(make_pair(l,-i));
}
for(int i=1;i<=n;i++)assert(i<R[i]);
for(int i=1;i<=n;i++){
vec[i].push_back(make_pair(L[i]+1,1));
vec[i].push_back(make_pair(i+1,-1));
vec[R[i]].push_back(make_pair(L[i]+1,-1));
vec[R[i]].push_back(make_pair(i+1,1));
}
for(int i=1;i<=n;i++){
for(auto it:vec[i])add(it.first,it.second);
for(auto it:qry[i]){
ans[it.second]+=query(it.first);
}
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
P6072 『MdOI R1』Path
我们以 \(1\) 为根,定义 \(a_x\) 为从 \(x\) 到 \(1\) 边权的异或值,\(in_x\) 和 \(out_x\) 为 \(x\) 子树内部和外部选择 \(u\) 和 \(v\) 中 \(a_u\oplus a_v\) 的最大值,那么最后答案显然为 \(\max_{x\ne1}\{in_x+out_x\}\) 。
对于求 \(in_x\) 我们有很多做法,例如启发式合并,\(\text{dsu on tree}\),或者可持久化 \(\text{trie}\),需要 \(O(n\log n\log\max w)\) 的时间。
对于求 \(out_x\) ,我们不妨先找到任意两个点 \(p\) 和 \(q\),使得 \(a_p\oplus a_q\) 最大,这时不以这个点对为最大值的只有 \(p\) 到根上的所有点,和 \(q\) 到根上的所有点。考虑我们只求树上一条链 \(out_x\) ,加入我们按照顺序遍历 到 \(x\),只需要将 \(x\) 父亲的子树内除去 \(x\) 子树部分加入 \(\text{trie}\) 即可。这一部分复杂度 \(O(n\log \max w)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,k,tot,totq,p=1,trie_tot=1,SZ,Ans;
int dfn[60005],siz[60005],bel[60005],dis[60005];
int fir[60005],we[120005],to[120005],nxt[120005];
int ch[1800005][2],tag[1800005],ans[60005];
struct ques {
int l,r,id;
ques(){ l=r=id=0;}
ques(int _l,int _r,int i){
l=_l,r=_r,id=i;
}
inline friend bool operator<(ques a,ques b){
if(bel[a.l]==bel[b.l])return a.r<b.r;
else return bel[a.l]<bel[b.l];
}
} q[120005];
inline void link(int u,int v,int w){
static int ce=0;
to[++ce]=v,we[ce]=w,nxt[ce]=fir[u],fir[u]=ce;
}
inline void insert(int a){
int u=1;
for(int i=30;i>=0;i--){
bool v=(a>>i)&1;
if(!ch[u][v])ch[u][v]=++trie_tot;
u=ch[u][v];tag[u]++;
}
}
inline int query(int a){
int u=1,res=0;
for(int i=30;i>=0;i--){
bool v=(a>>i)&1;
if(tag[ch[u][v^1]])res|=1<<i,u=ch[u][v^1];
else u=ch[u][v];
}
return res;
}
inline void del(int a){
int u=1;
for(int i=30;i>=0;i--){
bool v=(a>>i)&1;
u=ch[u][v];
tag[u]--;
}
}
void dfs(int u,int par,int w){
siz[u]=1,dfn[u]=++tot,dis[dfn[u]]=dis[dfn[par]]^w;
for(int i=fir[u],v;i;i=nxt[i]){
if((v=to[i])!=par){
int w=we[i];
dfs(v,u,w);
siz[u]+=siz[v];
}
}
}
inline int calc(int l,int r){
int res=0;
for(int i=l;i<=r;i++)res=max(res,query(dis[i])),insert(dis[i]);
for(int i=l;i<=r;i++)del(dis[i]);
return res;
}
inline void solve(int x){
int br=min(x*SZ,n),l=br+1,r=br;
int _ans=0;
for(;bel[q[p].l]==x;p++){
int ql=q[p].l,qr=q[p].r;
if(bel[qr]==x){ans[q[p].id]+=calc(ql,qr);continue;}
while(r<qr)_ans=max(_ans,query(dis[++r])),insert(dis[r]);
int rev=_ans;
while(l>ql)_ans=max(_ans,query(dis[--l])),insert(dis[l]);
ans[q[p].id]+=_ans;
while(l<=br)del(dis[l++]);_ans=rev;
}
while(r>br)del(dis[r--]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
link(u,v,w),link(v,u,w);
}
dfs(1,0,0);
for(int i=2;i<=n;i++)q[++totq]=ques(dfn[i],dfn[i]+siz[i]-1,i),q[++totq]=ques(dfn[i]+siz[i],dfn[i]+n-1,i);
for(int i=1;i<=n;i++)dis[i+n]=dis[i];
n<<=1;SZ=sqrt(n);
for(int i=1;i<=n;i++)bel[i]=(i-1)/ SZ+1;
sort(q+1,q+totq+1);
for(int i=1;i<=bel[n];i++)solve(i);
for(int i=1;i<=n;i++)Ans=max(Ans,ans[i]);
printf("%d\n",Ans);
return 0;
}
[Ynoi2006] rldcot
考虑扫描线,每次加入一个点 \(x\) 时将这个点到根的路径上标记上 \(x\) ,如果有某一段之前被标过号 \(y\) 了,那么这一段的底端就是点 \(x\) 和 \(y\) 的 \(\text{lca}\) ,用 \(y\) 更新一下这个点深度最后出现的位置即可,时间复杂度 \(O(n\log^2 n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int ver[200005],ne[200005],head[100005],cnt,len[200005];
inline void link(int x,int y,int v){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;len[cnt]=v;
}
long long dep[100005];
int fa[100005];
void dfs(int x,int fi){
fa[x]=fi;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dep[u]=dep[x]+len[i];
dfs(u,x);
}
}
vector<long long> hsh;
int son[2][100005],val[100005],lazy[100005];
inline bool isroot(int x){
return son[0][fa[x]]!=x&&son[1][fa[x]]!=x;
}
inline void push(int x){
if(lazy[x]){
val[son[0][x]]=lazy[son[0][x]]=lazy[x];
val[son[1][x]]=lazy[son[1][x]]=lazy[x];
}
lazy[x]=0;
}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y))son[son[1][z]==y][z]=x;
bool is=(son[1][y]==x);
son[is][y]=son[!is][x];fa[son[!is][x]]=y;
son[!is][x]=y;fa[y]=x;fa[x]=z;
}
int stk[100005],top;
inline void splay(int x){
stk[++top]=x;
for(int i=x;!isroot(i);i=fa[i])stk[++top]=fa[i];
while(top)push(stk[top--]);
while(!isroot(x)){
int y=fa[x],z=fa[y];
if(!isroot(y)){
if((son[1][z]==y)^(son[1][y]==z))rotate(x);
else rotate(y);
}rotate(x);
}
}
int pre[100005],tree[100005];
inline void add(int x,int v){
while(x){
tree[x]+=v;
x&=(x-1);
}
}
inline int query(int x){
int res=0;
while(x<=n){
res+=tree[x];
x+=(x&-x);
}
return res;
}
inline void access(int x){
int tmp=x,i=0;
add(pre[dep[x]],-1);pre[dep[x]]=x;add(pre[dep[x]],1);
while(x){
splay(x);add(pre[dep[x]],-1);
pre[dep[x]]=max(pre[dep[x]],val[x]);add(pre[dep[x]],1);
son[1][x]=i;i=x;x=fa[x];
}
val[i]=lazy[i]=tmp;
}
int ans[500005];
vector<pair<int,int> > vec[100005];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
link(x,y,v);link(y,x,v);
}
dfs(1,0);
for(int i=1;i<=n;i++)hsh.push_back(dep[i]);
sort(hsh.begin(),hsh.end());hsh.erase(unique(hsh.begin(),hsh.end()),hsh.end());
for(int i=1;i<=n;i++)dep[i]=upper_bound(hsh.begin(),hsh.end(),dep[i])-hsh.begin();
for(int i=1;i<=m;i++){
int l,r;scanf("%d%d",&l,&r);
vec[r].push_back(make_pair(l,i));
}
for(int i=1;i<=n;i++){
access(i);
for(auto it:vec[i])ans[it.second]=query(it.first);
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
P6071 『MdOI R1』Treequery
给定一个点 \(x\) ,询问 \(x\) 到 \([l,r]\) 内的点组成的虚树的距离,对于点 \(x\) 与虚树 \([l,r]\) 的关系分类讨论即可。
如果 \([l,r]\) 一部分在 \(x\) 子树内,一部分在子树外,答案为 \(0\) 。
如果 \([l,r]\) 全部在 \(x\) 子树内,答案为点 \(x\) 到 \([l,r]\) 的 \(\text{lca}\) 的距离。
如果 \([l,r]\) 全部在 \(x\) 子树外,答案为点 \(x\) 到其在 \([l,r]\) 内 \(\text{dfs}\) 序上的前驱和后继的之间的链的距离。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int ver[400005],ne[400005],head[400005],tot,val[400005];
inline void link(int x,int y,int v){
ver[++tot]=y;
ne[tot]=head[x];
head[x]=tot;val[tot]=v;
}
int siz[200005],son[200005],fa[200005];
long long dep[200005];
void dfs1(int x,int fi){
siz[x]=1;fa[x]=fi;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dep[u]=dep[x]+val[i];
dfs1(u,x);siz[x]+=siz[u];
if(siz[u]>siz[son[x]])son[x]=u;
}
}
int dfn[200005],cnt,top[200005],mp[200005];
void dfs2(int x,int fi){
dfn[x]=++cnt;top[x]=fi;mp[cnt]=x;
if(son[x])dfs2(son[x],fi);
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fa[x]||u==son[x])continue;
dfs2(u,u);
}
}
inline int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];swap(x,y);
}return dep[x]<dep[y]?x:y;
}
inline long long dist(int x,int y){
int lc=lca(x,y);
return dep[x]+dep[y]-2*dep[lc];
}
int tree[4000005],rt[200005],le[4000005],ri[4000005];
int insert(int loc,int old,int l=1,int r=n){
if(loc<l||loc>r)return old;
int i=++cnt;tree[i]=tree[old]+1;
if(l==r)return i;
int mid=(l+r)>>1;
le[i]=insert(loc,le[old],l,mid);ri[i]=insert(loc,ri[old],mid+1,r);
return i;
}
int query(int fr,int to,int a,int b,int l=1,int r=n){
if(fr>r||to<l)return 0;
if(fr<=l&&to>=r)return tree[b]-tree[a];
int mid=(l+r)>>1;
return query(fr,to,le[a],le[b],l,mid)+query(fr,to,ri[a],ri[b],mid+1,r);
}
int find(int k,int a,int b,int l=1,int r=n){
if(l==r)return mp[l];
int mid=(l+r)>>1;
if(tree[le[b]]-tree[le[a]]>=k)return find(k,le[a],le[b],l,mid);
return find(k-tree[le[b]]+tree[le[a]],ri[a],ri[b],mid+1,r);
}
long long lstans;
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<n;i++){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
link(x,y,v);link(y,x,v);
}
dfs1(1,1);dfs2(1,1);
for(int i=1;i<=n;i++)rt[i]=insert(dfn[i],rt[i-1]);
while(q--){
int x,l,r;scanf("%d%d%d",&x,&l,&r);
x^=lstans;l^=lstans;r^=lstans;
int tmp=query(dfn[x],dfn[x]+siz[x]-1,rt[l-1],rt[r]);
if(tmp==r-l+1){
int rk1=query(1,dfn[x]-1,rt[l-1],rt[r])+1,rk2=rk1+tmp-1;
rk1=find(rk1,rt[l-1],rt[r]);rk2=find(rk2,rt[l-1],rt[r]);
lstans=dist(lca(rk1,rk2),x);
}
else if(!tmp){
int rk1=query(1,dfn[x]-1,rt[l-1],rt[r]),rk2=(rk1!=r-l+1?rk1+1:0);
if(rk1)rk1=find(rk1,rt[l-1],rt[r]);if(rk2)rk2=find(rk2,rt[l-1],rt[r]);
if(rk1&&rk2)lstans=min(dep[x]-dep[lca(x,rk1)],dep[x]-dep[lca(x,rk2)]);
else {
int pre=find(1,rt[l-1],rt[r]),suf=find(r-l+1,rt[l-1],rt[r]);
if(rk1){
lstans=dist(x,lca(pre,rk1));
int t=lca(x,rk1);
if(dist(pre,rk1)==dist(pre,t)+dist(t,rk1))lstans=min(lstans,dep[x]-dep[t]);
}
else if(rk2){
lstans=dist(x,lca(suf,rk2));
int t=lca(x,rk2);
if(dist(suf,rk2)==dist(suf,t)+dist(t,rk2))lstans=min(lstans,dep[x]-dep[t]);
}
}
}
else {
lstans=0;
}
printf("%lld\n",lstans);
}
return 0;
}
[Ynoi2008] rrusq
考虑扫描线,对于每个点 \((i,p_i)\) 记录它最晚被覆盖到的时间,我们需要做的就是批量更新一个矩形内的点,并维护在每个时间点被覆盖的点的权值和。
对点建出 \(\text{KDT}\) , 每次询问在 \(\text{KDT}\) 上打标记并把子树内的所有标记收回。因为收回标记对应着打标记,所以复杂度仍然是 \(\Theta(m)\) 次遍历 \(\text{KDT}\) 的复杂度 , 即 \(\Theta(m\sqrt n)\) 次打标记/收回标记的操作。
现在我们要完成的问题变成了支持 \(\Theta(m\sqrt n)\) 次单点修改和 \(\Theta(m)\) 次后缀查询的数据结构 , 不难发现可以用 \(\Theta(1)-\Theta(\sqrt m)\) 的分块来实现它。
时间复杂度 \(\Theta(n\sqrt m+m\sqrt n)\) , 空间复杂度 \(\Theta(n+m)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,q,key;
struct node{
int x[2],v,sum;
int lx,rx,ly,ry;
node(int _x=0,int _y=0,int _v=0){
lx=rx=x[0]=_x;ly=ry=x[1]=_y;sum=v=_v;
}
inline bool operator <(const node &b)const{
return x[key]<b.x[key];
}
}a[100005],tree[100005];
int le[100005],ri[100005],cnt;
int build(int l=1,int r=n,int _=0){
if(l>r)return 0;
int i=++cnt,mid=(l+r)>>1;key=_;
nth_element(a+l,a+mid,a+r+1);tree[i]=a[mid];
le[i]=build(l,mid-1,_^1);ri[i]=build(mid+1,r,_^1);
if(le[i]){
tree[i].lx=min(tree[i].lx,tree[le[i]].lx);tree[i].ly=min(tree[i].ly,tree[le[i]].ly);
tree[i].rx=max(tree[i].rx,tree[le[i]].rx);tree[i].ry=max(tree[i].ry,tree[le[i]].ry);
tree[i].sum+=tree[le[i]].sum;
}
if(ri[i]){
tree[i].lx=min(tree[i].lx,tree[ri[i]].lx);tree[i].ly=min(tree[i].ly,tree[ri[i]].ly);
tree[i].rx=max(tree[i].rx,tree[ri[i]].rx);tree[i].ry=max(tree[i].ry,tree[ri[i]].ry);
tree[i].sum+=tree[ri[i]].sum;
}
return i;
}
int col[100005],tag[100005],lazy[100005];
inline void push(int i){
if(lazy[i]){
col[le[i]]=tag[le[i]]=lazy[le[i]]=lazy[i];
col[ri[i]]=tag[ri[i]]=lazy[ri[i]]=lazy[i];
}
lazy[i]=0;
}
inline void pushup(int i){
tag[i]=col[i];
if(le[i]&&tag[le[i]]!=tag[i])tag[i]=-1;
if(ri[i]&&tag[ri[i]]!=tag[i])tag[i]=-1;
}
namespace Block{
int s=500,sqr[100005],le[100005],ri[100005];
int a[100005],tot[505];
inline void init(){
for(int i=1;i<=m;i++)sqr[i]=i/s+1;
for(int i=1;i<=m;i++)ri[sqr[i]]=i;
for(int i=m;i>=1;i--)le[sqr[i]]=i;
}
inline void add(int x,int v){
a[x]+=v;tot[sqr[x]]+=v;
}
inline int query(int l,int r){
int res=0;
for(int i=l;i<=ri[sqr[l]]&&i<=r;i++)res+=a[i];
if(sqr[l]==sqr[r])return res;
for(int i=sqr[l]+1;i<sqr[r];i++)res+=tot[i];
for(int i=le[sqr[r]];i<=r;i++)res+=a[i];
return res;
}
}
void update(int lx,int rx,int ly,int ry,int v,int i=1){
if(!i||rx<tree[i].lx||lx>tree[i].rx||ry<tree[i].ly||ly>tree[i].ry)return ;
if(~tag[i]&&lx<=tree[i].lx&&tree[i].rx<=rx&&ly<=tree[i].ly&&tree[i].ry<=ry){
Block::add(tag[i],-tree[i].sum);
col[i]=tag[i]=lazy[i]=v;
Block::add(tag[i],tree[i].sum);return ;
}push(i);
if(lx<=tree[i].x[0]&&tree[i].x[0]<=rx&&ly<=tree[i].x[1]&&tree[i].x[1]<=ry){
Block::add(col[i],-tree[i].v);
col[i]=tag[i]=v;
Block::add(col[i],tree[i].v);
}
update(lx,rx,ly,ry,v,le[i]);
update(lx,rx,ly,ry,v,ri[i]);
pushup(i);
}
int X1[100005],X2[100005],Y1[100005],Y2[100005];
vector<pair<int,int> > vec[100005];
int ans[1000005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x,v;scanf("%d%d",&x,&v);
a[i]=node(i,x,v);
}
build();
scanf("%d",&m);Block::init();
for(int i=1;i<=m;i++)scanf("%d%d%d%d",&X1[i],&X2[i],&Y1[i],&Y2[i]);
scanf("%d",&q);
for(int i=1;i<=q;i++){
int l,r;scanf("%d%d",&l,&r);
vec[r].push_back(make_pair(l,i));
}
for(int i=1;i<=m;i++){
update(X1[i],X2[i],Y1[i],Y2[i],i);
for(auto it:vec[i]){
ans[it.second]=Block::query(it.first,i);
}
}
for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
return 0;
}
转转的数据结构题
考虑扫描线,\(\text{ODT}\) 维护序列上的连续段,树状数组维护每次操作剩余的对序列的贡献。
点击查看代码
[Ynoi2009] rprmq1
考虑扫描线,因为这个信息不具有可减性,考虑对平面进行分治,每次分治的时候,处理跨过分治中线的询问。
这样一来就把询问拆成了分治中线两侧的一个前缀和一个后缀。
然后我们只需对两边分别做扫描线,维护扫过部分的历史最大值即可,为了让分治的复杂度正确,可以和莫队类似,试着利用之前的信息。
图中是分治的过程,其中红线表示撤回一些修改,并将历史最值设为当前值。后者打标记即可,不过打标记之前要将节点原有的加法标记下传.
否则如果它的加法标记为负数,这个标记将无法加到其子节点的历史最值上。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
long long mx[400005],hmx[400005],lazy[400005],hlazy[400005];
bool cov[400005];
inline void add(int i,long long v,long long hv){
hlazy[i]=max(hlazy[i],lazy[i]+hv);
hmx[i]=max(hmx[i],mx[i]+hv);mx[i]+=v;lazy[i]+=v;
}
inline void reset(int i){
if(lazy[i]||hlazy[i]){
add(i<<1,lazy[i],lazy[i]);add(i<<1|1,lazy[i],lazy[i]);
lazy[i]=hlazy[i]=0;
}
hmx[i]=mx[i];hlazy[i]=lazy[i];cov[i]=1;
}
inline void push(int i){
if(cov[i]){
reset(i<<1);reset(i<<1|1);
cov[i]=0;
}
if(lazy[i]||hlazy[i]){
add(i<<1,lazy[i],hlazy[i]);add(i<<1|1,lazy[i],hlazy[i]);
lazy[i]=hlazy[i]=0;
}
}
void update(int fr,int to,long long v,int l=1,int r=n,int i=1){
if(fr>r||to<l)return ;
if(fr<=l&&to>=r){
add(i,v,v);return ;
}
int mid=(l+r)>>1;push(i);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
mx[i]=max(mx[i<<1],mx[i<<1|1]);
hmx[i]=max(hmx[i<<1],hmx[i<<1|1]);
}
long long query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return -1e18;
if(fr<=l&&to>=r)return hmx[i];
int mid=(l+r)>>1;push(i);
return max(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
}
struct data{
int l,r,v;
data(int _l,int _r,int _v){
l=_l;r=_r;v=_v;
}
};
vector<data> L[50005],R[50005];
int le[500005],ri[500005],up[500005],down[500005];
long long ans[500005];
vector<int> qryl[50005],qryr[50005];
void solve(int l,int r,int mid){
// cout<<"solve "<<l<<" "<<r<<" "<<mid<<endl;
for(int i=mid;i>=l;i--){
for(auto it:R[i])update(it.l,it.r,it.v);
for(auto it:qryl[i]){
if(ri[it]>=mid&&ri[it]<=r)ans[it]=max(ans[it],query(down[it],up[it]));
//,cout<<"ri "<<it<<" "<<ans[it]<<endl;
}
for(auto it:L[i])update(it.l,it.r,-it.v);
}
if(l!=r){
int midl=(l+mid)>>1;
for(int i=l;i<=midl;i++){
for(auto it:R[i])update(it.l,it.r,-it.v);
for(auto it:L[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
solve(l,mid,midl);
for(int i=midl+1;i<=mid;i++){
for(auto it:R[i])update(it.l,it.r,-it.v);
for(auto it:L[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
}
else {
for(int i=l;i<=mid;i++){
for(auto it:R[i])update(it.l,it.r,-it.v);
for(auto it:L[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
}
for(int i=mid+1;i<=r;i++){
for(auto it:L[i])update(it.l,it.r,it.v);
for(auto it:qryr[i]){
if(le[it]<=mid+1&&le[it]>=l)ans[it]=max(ans[it],query(down[it],up[it]));
//,cout<<"le "<<it<<" "<<ans[it]<<endl;
}
for(auto it:R[i])update(it.l,it.r,-it.v);
}
if(l!=r){
int midr=(mid+1+r)>>1;
for(int i=r;i>midr;i--){
for(auto it:L[i])update(it.l,it.r,-it.v);
for(auto it:R[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
solve(mid+1,r,midr);
for(int i=midr;i>mid;i--){
for(auto it:L[i])update(it.l,it.r,-it.v);
for(auto it:R[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
}
else {
for(int i=r;i>=mid+1;i--){
for(auto it:L[i])update(it.l,it.r,-it.v);
for(auto it:R[i])update(it.l,it.r,it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
}
// cout<<"solved "<<l<<" "<<r<<" "<<mid<<endl;
}
inline int read(){
int res=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
return res;
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++){
int l1,l2,r1,r2,v;l1=read();l2=read();r1=read();r2=read();v=read();
L[l1].push_back(data(l2,r2,v));
R[r1].push_back(data(l2,r2,v));
}
for(int i=1;i<=q;i++){
le[i]=read();down[i]=read();ri[i]=read();up[i]=read();
qryl[le[i]].push_back(i);
qryr[ri[i]].push_back(i);
}
int mid=(1+n)>>1;
for(int i=1;i<=mid;i++){
for(auto it:L[i])update(it.l,it.r,it.v);
for(auto it:R[i])update(it.l,it.r,-it.v);
}
hmx[1]=mx[1];hlazy[1]=lazy[1];cov[1]=1;
solve(1,n,mid);
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
return 0;
}
[Ynoi2008] rupq
使用平衡树维护序列,这样可以支持分裂合并。
虽然 \(\text{NAND}\) 没有结合律,但是可以维护它的真值表,使得 \(\max\) 和 \(\text{NAND}\) 都可以写做半群信息的形式,所以直接 \(O(1)\) 合并即可,每个区间的括号合并完成后应该是形如 ))))((((
的样子。
考虑怎么合并两个儿子的信息,不失一般性,设左儿子的 (
数量多于右儿子的 )
数量,其它的情况类似。
那么匹配完成后分为三部分,左儿子的 ))))
,左儿子的 (
和右儿子的 )
匹配之后左儿子剩下的 ((
,右儿子的 ((((
。
假如我们分别维护了 ))))
和 ((((
对应的信息,那么我们只需要考虑中间的部分如何维护。
计算左儿子的前若干个 (
的信息,于是实现函数 \(\text{GetL(u,k)}\) 表示计算节点 \(u\) 的前 \(k\) 个 (
的信息。
每次只需要向一边递归,复杂度 \(O(\log n)\),总时间复杂度 \(O(n+m\log^2n)\) 。
点击查看代码
#515. 【UR #19】前进四
容易想到一种类似于楼房重建的方式做到 \(O(n\log^2n)\) ,但是这是不够的,考虑单 \(\log\) 做法。
发现询问都为后缀,以时间为轴开线段树,倒序扫描序列,对于每个位置,每个数出现的时间都是一段区间,吉如一线段树维护每个位置被取 \(\min\) 的次数即可,时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
struct data{
int x,l,r;
data(int _x,int _l,int _r){
x=_x;l=_l;r=_r;
}
};
vector<data> vec[1000005];
vector<int> qry[1000005];
struct node{
int mx1,mx2;
node(){}
node(int _mx1,int _mx2){
mx1=_mx1;mx2=_mx2;
}
inline node operator +(const node &b)const{
if(mx1>b.mx1)return node(mx1,max(mx2,b.mx1));
if(mx1<b.mx1)return node(b.mx1,max(b.mx2,mx1));
return node(mx1,max(mx2,b.mx2));
}
}tree[4000005];
int lazy[4000005];
inline void push(int i){
if(!lazy[i])return ;
if(tree[i<<1].mx1>tree[i].mx1)tree[i<<1].mx1=tree[i].mx1,lazy[i<<1]+=lazy[i];
if(tree[i<<1|1].mx1>tree[i].mx1)tree[i<<1|1].mx1=tree[i].mx1,lazy[i<<1|1]+=lazy[i];
lazy[i]=0;
}
void build(int l=0,int r=q,int i=1){
tree[i]=node(1e9,0);
if(l==r)return ;
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
}
void update(int fr,int to,int v,int l=0,int r=q,int i=1){
if(fr>r||to<l||tree[i].mx1<=v)return ;
if(fr<=l&&to>=r&&tree[i].mx2<v){
lazy[i]++;tree[i].mx1=v;
return ;
}
int mid=(l+r)>>1;push(i);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
int query(int loc,int l=0,int r=q,int i=1){
if(loc<l||loc>r)return 0;
if(l==r)return lazy[i];
int mid=(l+r)>>1;push(i);
return query(loc,l,mid,i<<1)+query(loc,mid+1,r,i<<1|1);
}
int ans[1000005];
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
vec[i].push_back(data(x,0,q));
}
for(int i=1;i<=q;i++){
int op,x,y;
scanf("%d",&op);
if(op==1){
scanf("%d%d",&x,&y);
vec[x].back().r=i-1;vec[x].push_back(data(y,i,q));
}
else if(op==2){
scanf("%d",&x);qry[x].push_back(i);
}
}
build();
for(int i=n;i;i--){
for(auto it:vec[i])update(it.l,it.r,it.x);
for(auto it:qry[i])ans[it]=query(it);
}
for(int i=1;i<=q;i++)if(ans[i])printf("%d\n",ans[i]);
return 0;
}