树据结构2
线段树合并
P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并
先差分离线,转化成单点加。如果暴力做的话然后我们能够得到 \(n\) 个大小为 \(10^5\) 维护每个数出现次数的桶。由于差分,我们还要做一个前缀和,显然不能暴力将桶相加。
考虑每个节点搞一个动态开点线段树替换桶,然后我们要将这些线段树的信息合并起来。合并方法其实很暴力,就是把对应的点权相加。唯一的优化就是如果遇到两颗线段树其中一个点是空结点直接就可以返回了。
复杂度是正确的,因为我们合并的复杂度是两颗线段树有结点的重叠部分,但是我们的动态开点线段树总共就 \(O(n)\) 级别的修改,极限的也就是每个修改相同然后每次合并一条链,这样是 \(O(n\log n)\) 的。
关于空间,考虑先递归子树再在自己这里加贡献,空间可以做到 \(O(n\log n)\),但是数组能开还是要开。
#include<bits/stdc++.h>
#define mid (l+r>>1)
#define mk make_pair
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int N=1e5+5,S=4e6;
int n,m,tot;
vector<int> e[N];
vector<pii> g[N];
int rt[N],dep[N],f[N][20],ans[N];
int ls[S],rs[S],s1[S],s2[S];
void push_up(int p){
s1[p]=max(s1[ls[p]],s1[rs[p]]);
s2[p]=(s1[p]==s1[ls[p]]?s2[ls[p]]:s2[rs[p]]);
}void update(int &p,int l,int r,int x,int y){
if(!p)p=++tot;
if(l==r)return s1[p]+=y,s2[p]=l,void();
if(x<=mid)update(ls[p],l,mid,x,y);
else update(rs[p],mid+1,r,x,y);
push_up(p);
}int merge(int p,int q,int l,int r){
if(!p||!q)return p|q;
if(l==r)return s1[p]+=s1[q],s2[p]=l,p;
ls[p]=merge(ls[p],ls[q],l,mid);
rs[p]=merge(rs[p],rs[q],mid+1,r);
return push_up(p),p;
}void dfs1(int u){
for(int i=1;i<=18;++i)
f[u][i]=f[f[u][i-1]][i-1];
for(int v:e[u])
if(v!=f[u][0])
f[v][0]=u,dep[v]=dep[u]+1,dfs1(v);
}int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
for(int i=18;i>=0;--i)
if(dep[f[u][i]]>=dep[v])
u=f[u][i];
if(u==v)return u;
for(int i=18;i>=0;--i)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return f[u][0];
}void dfs2(int u){
for(int v:e[u])
if(v!=f[u][0])
dfs2(v),rt[u]=merge(rt[u],rt[v],0,100001);
for(pii v:g[u])
update(rt[u],0,100001,v.first,v.second);
ans[u]=s2[rt[u]];
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<n;++i)
cin>>u>>v,e[u].pb(v),e[v].pb(u);
dfs1(dep[1]=1);int x,y,z;
while(m--){
cin>>x>>y>>z;int l=lca(x,y);
g[x].pb(mk(z,1)),g[y].pb(mk(z,1));
g[l].pb(mk(z,-1)),g[f[l][0]].pb(mk(z,-1));
}dfs2(1);
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
return 0;
}
P3224 [HNOI2012] 永无乡
合并两个联通块和查询块内第 \(k\) 小。
做法很显然,在线段树基础上用并查集维护连通性即可。
P3899 [湖南集训] 更为厉害
显然,\(c\) 是 \(a,b\) 公共子树内的结点。
对于 \(b\) 在 \(a\) 上面的情况,答案为 \(\min(dep_a-1,k)\times (siz_a-1)\)。
对于 \(b\) 在 \(a\) 下面的情况,考虑一个 \(b\in \mathrm{subtree}(a)\),对答案的贡献为 \(siz_b-1\)。我们现在要查询 \(a\) 子树中与自己距离在 \(k\) 以内 \(siz\) 和。
考虑有两个限制,一个是在 \(\mathrm{subtree}(a)\) 中,这个可以用 dfs 序来维护。另一个是距离,由于是在子树中,所以用 \(dep\) 就可以解决。
考虑按照 dfs 序建主席树,每次总 \(dfn_u-1\) 的结点传过来,在 \(dep_u\) 这里加上 \(siz_u-1\)。当然也可以按照 \(dep\) 建,然后在 \(dfn\) 处加上,就是内外嵌套的区别。
所以跟线段树合并有什么关系呢?你可以用线段树合并当主席树用。
#include<bits/stdc++.h>
#define int long long
#define mid (l+r>>1)
#define pb push_back
using namespace std;
const int N=3e5+5,S=6e6+5;
vector<int> e[N];
int n,q,rt[N],tot;
int siz[N],dep[N],dfn[N],cnt;
struct node{int ls,rs,w;}a[S];
void update(int &p,int lp,int l,int r,int x,int y){
a[p=++tot]=a[lp];a[p].w+=y;if(l==r)return;
if(x<=mid)update(a[p].ls,a[lp].ls,l,mid,x,y);
else update(a[p].rs,a[lp].rs,mid+1,r,x,y);
}int query(int p,int lp,int l,int r,int L,int R,int res=0){
if(L<=l&&r<=R)return a[p].w-a[lp].w;
if(L<=mid)res+=query(a[p].ls,a[lp].ls,l,mid,L,R);
if(mid<R)res+=query(a[p].rs,a[lp].rs,mid+1,r,L,R);
return res;
}void dfs1(int u,int fa){
for(int v:e[u])if(v!=fa)
dep[v]=dep[u]+1,dfs1(v,u),siz[u]+=siz[v];
++siz[u];
}void dfs2(int u,int fa){
dfn[u]=++cnt;
update(rt[dfn[u]],rt[dfn[u]-1],1,n,dep[u],siz[u]-1);
for(int v:e[u])if(v!=fa)
dfs2(v,u);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
for(int i=1,u,v;i<n;++i)
cin>>u>>v,e[u].pb(v),e[v].pb(u);
dep[1]=1,dfs1(1,0),dfs2(1,0);
int p,k;while(q--){
cin>>p>>k;
cout<<query(rt[dfn[p]+siz[p]-1],rt[dfn[p]-1],1,n,dep[p]+1,dep[p]+k)+min(k,dep[p]-1)*(siz[p]-1)<<'\n';
}
return 0;
}
CF600E Lomsat gelral
线段树合并板题,注意 push_up 时左右相等的情况即可。
CF1051G Distinctification
给你一个长度为 \(n\) 的整数对序列 \(P=\{(a_1,b_1),(a_2,b_2),...,(a_n,b_n)\}\),你可以进行以下 \(2\) 种操作若干次:
- 存在 \(i\not=j,a_i=a_j\) 时,令 \(a_i\gets a_i+1\),花费 \(b_i\)。
- 存在 \(i\not=j,a_i=a_j+1\) 时,令 \(a_i\gets a_i-1\),花费 \(-b_i\)。
定义 \(f(S)\) 为使得 \(S\) 中所有 \(a_i\) 互补相同的最小花费。对于每一个 \(i\in[1,n]\),求出 \(f(P_{[1,i]})\)。
什么破题没有翻译。
记 \(op_{1,i,j},op_{2,i,j}\) 为如上的操作 \(1,2\)。
两个操作是互逆的。如果存在 \(a_i=a_j+1\),我们进行 \(op_{2,i,j},op_{1,j,i}\),就能实现交换 \(b_i,b_j\)(等效于交换 \(a_i,a_j\)),并且有 \(b_j-b_i\) 的费用。
知道操作的效果后,我们发现,我们实际上是对于 \(a\) 在值域上的连续一段重排,设重排后变为 \(A\),费用就是 \(\sum_i b_i(A_i-a_i)=\sum_i b_iA_i-b_ia_i\),后者为定值,转化成最小化 \(\sum_i b_iA_i\)。
对于一个值域连续段,设其大小 \(m\),最终我们得到一个 \(a_i<a_{i+1}<...<a_{i+m-1}\),对应的 \(b\) 有 \(b_{i}>b_{i+1}>...>b_{i+m-1}\)。这样的数列可能和后面有交,所以一直合并下去直到结束。
现在是一个数据结构问题。上面是分配的策略,但具体地,设 \(v\) 为 \(a_i\) 所在连续段的最小值,\(rk_{b_i}\) 为 \(b_i\) 在所在连续段的第 \(rk_{b_i}\) 大,我们要维护 \(\sum_i b_i(v+rk_{b_i}-1)=(v-1)\sum_ib_i+\sum_ib_irk_{b_i}\)。前者很好维护,重点是后者。
考虑从前到后,每次插入一个数。我们会新建一个连续段,在一个连续段插入数,合并两个连续段。新建就新建好了。在连续段插入时,我们将 \(a_i\) 设为所在连续段最大值然后转化成就是新建加合并。
我们可以用线段树合并!用若干线段树维护每个连续段的 \(b\)。在合并的过程中,右子树的每个元素会使得左子树所有元素排名增加 \(1\),使用类似分治的思想维护 \(ans\) 即可。同时维护 \((v-1)\sum_ib_i\)。除此之外,我们要维护每个区间的左右端点,用并查集科技维护就行。
#include<bits/stdc++.h>
#define int long long
#define mid (l+r>>1)
using namespace std;
const int N=4e5+5;
int n,fa[N],ans;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int rt[N],tot;bool isp[N];
struct node{int ls,rs,w,v;}a[N*32];
void update(int &p,int l,int r,int x){
a[p=++tot]={0,0,1,x};if(l==r)return;
if(x<=mid)update(a[p].ls,l,mid,x);
else update(a[p].rs,mid+1,r,x);
}int merge(int x,int y,int l,int r){
if(!x||!y)return x|y;
ans+=a[a[x].ls].v*a[a[y].rs].w+a[a[y].ls].v*a[a[x].rs].w;
a[x].ls=merge(a[x].ls,a[y].ls,l,mid);
a[x].rs=merge(a[x].rs,a[y].rs,mid+1,r);
a[x].w+=a[y].w,a[x].v+=a[y].v;
return x;
}void calc(int x,int y){
fa[x]=y;
ans-=a[rt[y]].v*a[rt[x]].w;
merge(rt[y],rt[x],1,n);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;for(int i=0;i<N;++i)fa[i]=i;
for(int i=1;i<=n;++i){
int x,y;cin>>x>>y;
if(isp[x])ans+=y*(find(x)+1-x),x=find(x)+1;
isp[x]=1;update(rt[x],1,n,y);
if(isp[x-1])calc(x-1,x);
if(isp[x+1])calc(x,find(x+1));
cout<<ans<<'\n';
}
return 0;
}
线段树分裂
P5494 【模板】线段树分裂
话说天下大势,分久必合,合久必分。
合并讲过了,分裂长得和 fhq 很像,因为我们是分裂出一个连续段,所以就想普通线段树区间查询一样,复杂度是 \(O(\log n)\) 的。
由于要不断分裂和合并,最好将删除的点回收,这样空间负担会小很多。
这题也可以用平衡树做。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,S=6e6+5;
int n,m;
int rt[N],cnt=1,tot;
int rec[S],top;
struct node{int ls,rs,w;}a[S];
inline void clear(int p){rec[++top]=p;a[p].ls=a[p].rs=a[p].w=0;}
inline int get(){return (top?rec[top--]:++tot);}
inline void merge(int &p,int q){
if(!p||!q){p=p+q;return;}
a[p].w+=a[q].w;
merge(a[p].ls,a[q].ls);
merge(a[p].rs,a[q].rs);
clear(q);
}inline void split(int x,int &y,int k){
if(x==0)return;
y=get();
int v=a[a[x].ls].w;
if(k>v)split(a[x].rs,a[y].rs,k-v);
else swap(a[x].rs,a[y].rs);
if(k<v)split(a[x].ls,a[y].ls,k);
a[y].w=a[x].w-k;
a[x].w=k;
}inline void update(int &p,int l,int r,int x,int v){
if(!p)p=get();a[p].w+=v;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(a[p].ls,l,mid,x,v);
else update(a[p].rs,mid+1,r,x,v);
}inline int query1(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)
return a[p].w;
int mid=(l+r)>>1,sum=0;
if(L<=mid)sum+=query1(a[p].ls,l,mid,L,R);
if(mid<R)sum+=query1(a[p].rs,mid+1,r,L,R);
return sum;
}inline int query2(int p,int l,int r,int k){
if(l==r)return l;
int mid=(l+r)>>1;
if(k<=a[a[p].ls].w)return query2(a[p].ls,l,mid,k);
else return query2(a[p].rs,mid+1,r,k-a[a[p].ls].w);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int op,p,x,y;
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>x,update(rt[1],1,n,i,x);
while(m--){
cin>>op>>p>>x;
if(op==0){
cin>>y;
int s1=query1(rt[p],1,n,1,y);
int s2=query1(rt[p],1,n,x,y);
int tmp=0;
split(rt[p],rt[++cnt],s1-s2);
split(rt[cnt],tmp,s2);
merge(rt[p],tmp);
}
if(op==1)
merge(rt[p],rt[x]);
if(op==2)
cin>>y,update(rt[p],1,n,y,x);
if(op==3)
cin>>y,cout<<query1(rt[p],1,n,x,y)<<'\n';
if(op==4){
if(a[rt[p]].w<x)cout<<"-1\n";
else cout<<query2(rt[p],1,n,x)<<'\n';
}
}
return 0;
}
P2824 [HEOI2016/TJOI2016] 排序
发现排序这种东西它直接合并是并不起来的,但是我们可以用桶排,这样合并两个区间就是线段树的事情了。
具体的,使用 set 维护若干已经拍过序的连续段,这一个段我们全放到一个线段树做的桶中。对于一个区间的排序,我们将这个区间的若干棵树抠出来合并,并且标记是正着排还是反着排。扣的时候可能要将边上的线段树分裂开。由于区间段数是 \(O(n)\) 的,每合并一次会使段数减一,每次至多分裂出两段,所以复杂度为 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
const int N=1e5+5,S=3e6+5;
int n,m,q;
set<int>st;
int up[N],rt[N],rec[S],top,tot;
struct node{int ls,rs,w;}a[S];
inline void clear(int p){rec[++top]=p;a[p].ls=a[p].rs=a[p].w=0;}
inline int get(){return (top?rec[top--]:++tot);}
void update(int &p,int l,int r,int x){
++a[p=get()].w;if(l==r)return;
if(x<=mid)update(a[p].ls,l,mid,x);
else update(a[p].rs,mid+1,r,x);
}int query(int p,int l,int r){
if(l==r)return l;
if(a[p].ls)return query(a[p].ls,l,mid);
else return query(a[p].rs,mid+1,r);
}void merge(int &p,int q){
if(!p||!q)return p=max(p,q),void();
merge(a[p].ls,a[q].ls);
merge(a[p].rs,a[q].rs);
a[p].w+=a[q].w;clear(q);
}void split(int p,int &q,int k,int op){
if(a[p].w==k)return;
a[q=get()].w=a[p].w-k;a[p].w=k;
if(op)swap(a[p].ls,a[p].rs);
if(k<=a[a[p].ls].w)split(a[p].ls,a[q].ls,k,op),swap(a[q].rs,a[p].rs);
else split(a[p].rs,a[q].rs,k-a[a[p].ls].w,op);
if(op)swap(a[p].ls,a[p].rs),swap(a[q].ls,a[q].rs);
}set<int>::iterator split(int x){
auto v=st.lower_bound(x);
if(*v==x)return v;--v;
split(rt[*v],rt[x],x-*v,up[x]=up[*v]);
return st.insert(x).first;
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m,st.insert(n+1);
for(int i=1,x;i<=n;++i)
cin>>x,update(rt[i],1,n,x),st.insert(i);
while(m--){
int op,l,r;cin>>op>>l>>r;
auto ll=split(l),rr=split(r+1);
for(auto m=++ll;m!=rr;++m)
merge(rt[l],rt[*m]);
up[l]=op,st.erase(ll,rr);
}cin>>q;split(q),split(q+1);
cout<<query(rt[q],1,n)<<'\n';
return 0;
}
树套树
空间较高,慎用。
P3380 【模板】树套树
除了修改,其它可以用主席树轻松维护。
我们使用主席树维护了一些二维的信息,但是我们如果要对这些二维的信息进行修改,主席树类似前缀和的方式会炸掉。
回想前缀差分离散化之后我们学习了树状数组和线段树,于是我们将主席树的前缀和方式换成树状数组(线段树)就行了。在实现上,我们直接将所有的根存下来,然后在查询时一遍扫掉,会省很多常数。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,INF=2147483647;
int len;
int n,m,tot,root[N];
int tem[N],tmp[N],cnt,num;
int lsh[N*2],a[N];
struct ques{int a,b,c,d;}q[N];
struct node{int w,ls,rs;}t[N<<7];
inline int lb(int x){return x&(-x);}
inline void push_up(int p){
t[p].w=t[t[p].ls].w+t[t[p].rs].w;
}
inline void change(int &p,int l,int r,int k,int v){
if(!p)p=++tot;
if(l==r){t[p].w+=v;return;}
int mid=(l+r)>>1;
if(k<=mid)change(t[p].ls,l,mid,k,v);
else change(t[p].rs,mid+1,r,k,v);
push_up(p);
}
inline void add(int p,int v){
for(int i=p;i<=n;i+=lb(i))change(root[i],1,len,a[p],v);
}
inline int query_num(int l,int r,int k){
if(l==r)return l;
int mid=(l+r)>>1,sum=0;
for(int i=1;i<=cnt;++i)sum+=t[t[tem[i]].ls].w;
for(int i=1;i<=num;++i)sum-=t[t[tmp[i]].ls].w;
if(k<=sum){
for(int i=1;i<=cnt;++i)tem[i]=t[tem[i]].ls;
for(int i=1;i<=num;++i)tmp[i]=t[tmp[i]].ls;
return query_num(l,mid,k);
}
else{
for(int i=1;i<=cnt;++i)tem[i]=t[tem[i]].rs;
for(int i=1;i<=num;++i)tmp[i]=t[tmp[i]].rs;
return query_num(mid+1,r,k-sum);
}
}
inline int find_num(int l,int r,int k){
cnt=num=0;
for(int i=r;i;i-=lb(i))
tem[++cnt]=root[i];
for(int i=l-1;i;i-=lb(i))
tmp[++num]=root[i];
return query_num(1,len,k);
}
inline int query_rnk(int l,int r,int k){
if(l==r)return 0;
int mid=(l+r)>>1,sum=0;
if(k<=mid){
for(int i=1;i<=cnt;++i)tem[i]=t[tem[i]].ls;
for(int i=1;i<=num;++i)tmp[i]=t[tmp[i]].ls;
return query_rnk(l,mid,k);
}
else{
for(int i=1;i<=cnt;++i)sum+=t[t[tem[i]].ls].w,tem[i]=t[tem[i]].rs;
for(int i=1;i<=num;++i)sum-=t[t[tmp[i]].ls].w,tmp[i]=t[tmp[i]].rs;
return sum+query_rnk(mid+1,r,k);
}
}
inline int find_rnk(int l,int r,int k){
cnt=num=0;
for(int i=r;i;i-=lb(i))
tem[++cnt]=root[i];
for(int i=l-1;i;i-=lb(i))
tmp[++num]=root[i];
return query_rnk(1,len,k)+1;
}
inline int find_pri(int l,int r,int k){
int rk=find_rnk(l,r,k)-1;
if(rk==0)return 0;
return find_num(l,r,rk);
}
inline int find_nxt(int l,int r,int k){
if(k==len)return len+1;
int rk=find_rnk(l,r,k+1);
if(rk==r-l+2)return len+1;
return find_num(l,r,rk);
}
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],lsh[++len]=a[i];
for(int i=1;i<=m;++i){
cin>>q[i].a>>q[i].b>>q[i].c;
if(q[i].a!=3)cin>>q[i].d;
else lsh[++len]=q[i].c;
if(q[i].a==4||q[i].a==5)lsh[++len]=q[i].d;
}
sort(lsh+1,lsh+len+1);len=unique(lsh+1,lsh+len+1)-lsh-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(lsh+1,lsh+len+1,a[i])-lsh,add(i,1);
lsh[0]=-INF,lsh[len+1]=INF;
for(int i=1;i<=m;++i){
if(q[i].a==1){
q[i].d=lower_bound(lsh+1,lsh+len+1,q[i].d)-lsh;
cout<<find_rnk(q[i].b,q[i].c,q[i].d)<<'\n';
}
if(q[i].a==2){
cout<<lsh[find_num(q[i].b,q[i].c,q[i].d)]<<'\n';
}
if(q[i].a==3){
add(q[i].b,-1);
a[q[i].b]=lower_bound(lsh+1,lsh+len+1,q[i].c)-lsh;
add(q[i].b,1);
}
if(q[i].a==4){
q[i].d=lower_bound(lsh+1,lsh+len+1,q[i].d)-lsh;
cout<<lsh[find_pri(q[i].b,q[i].c,q[i].d)]<<'\n';
}
if(q[i].a==5){
q[i].d=lower_bound(lsh+1,lsh+len+1,q[i].d)-lsh;
cout<<lsh[find_nxt(q[i].b,q[i].c,q[i].d)]<<'\n';
}
}
return 0;
}
P3810 【模板】三维偏序(陌上花开)
这里只讲树套树的做法。
做二维偏序时(或者逆序对),我们使用排序去掉第一维(逆序对就不用排序了),然后用树状数组维护前面 \(<x\) 或 \(>x\) 的个数。现在我们使用树套树,外面的树状数组存 \(b\) 的出现,内层线段树再维护 \(c\) 的出现次数,于是有了 \(n\log^2 n\) 的做法。
注意在处理 \(a\) 相等的情况时,我们将所有 \(a\) 相等的一起加入,一起查询就可以了。
#include<bits/stdc++.h>
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
using namespace std;
const int N=1e5+5;
int n,k;
int ans[N],f[N];
int rt[N<<2],tmp[N<<2],tot,cnt;
struct ques{int a,b,c,id;}q[N];
inline bool cmp(ques x,ques y){
if(x.c==y.c){
if(x.b==y.b)return x.a<y.a;
return x.b<y.b;
}
return x.c<y.c;
}
struct node{int ls,rs,v;}a[N<<7];
void push_up(int p){a[p].v=a[a[p].ls].v+a[a[p].rs].v;}
void change(int &p,int l,int r,int x,int v){
if(!p)p=++tot;
if(l==r){a[p].v+=v;return;}
int mid=(l+r)>>1;
if(x<=mid)change(a[p].ls,l,mid,x,v);
else change(a[p].rs,mid+1,r,x,v);
push_up(p);
}
void add(int p,int x,int v){
for(int i=p;i<=k;i+=i&-i)change(rt[i],1,k,x,v);
}
int query(int l,int r,int w){
if(l==r){
int res=0;
for(int i=1;i<=cnt;++i)res+=a[tmp[i]].v;
return res;
}
int mid=(l+r)>>1,sum=0;
if(w<=mid){
for(int i=1;i<=cnt;++i)tmp[i]=a[tmp[i]].ls;
return query(l,mid,w);
}
else{
for(int i=1;i<=cnt;++i)
sum+=a[a[tmp[i]].ls].v,tmp[i]=a[tmp[i]].rs;
return sum+query(mid+1,r,w);
}
}
int find(int r,int w){
cnt=0;
for(int i=r;i;i-=i&-i)
tmp[++cnt]=rt[i];
return query(1,k,w);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=k;++i)rt[i]=++tot;
for(int i=1;i<=n;++i)
cin>>q[i].a>>q[i].b>>q[i].c,q[i].id=i;
sort(q+1,q+n+1,cmp);
for(int i=1,j;i<=n;){
j=i;
while(q[j].c==q[j+1].c)
add(q[j].a,q[j].b,1),++j;
add(q[j].a,q[j].b,1),++j;
j=i;
while(q[j].c==q[j+1].c)
f[q[j].id]=find(q[j].a,q[j].b),++j;
f[q[j].id]=find(q[j].a,q[j].b);
i=j+1;
}
for(int i=1;i<=n;++i)++ans[f[i]];
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
return 0;
}
P3157 [CQOI2011] 动态逆序对
我们在依次删除时,要减去前面比它大的,后面比它小的,且要带修改,也就是树套树板子。
P2617 Dynamic Rankings
比模板题还简单,直接树套树。
李超树
P4097 【模板】李超线段树 / [HEOI2013Segment]
都说了李超树,所以要上线段树。
考虑我们新加入一个线段 \(x\) 到区间 \([l,r]\),设 \(mid=\frac{l+r}2\) 处的最优线段为 \(y\)。
如果没有线段,直接用 \(x\) 更新,然后结束递归。
如果 \(m\) 处 \(x\) 比 \(y\) 优,那么更新此节点,交换 \(x,y\),此时 \(x\) 在 \(m\) 处不如 \(y\) 优。
- 如果左端点 \(x\) 更优,则向左递归。
- 如果右端点 \(x\) 更优,则向右递归。
- 如果都不优,不用递归。
因为 \(x\) 不如 \(y\) 优且交点至多一个,所以只会向一侧递归,复杂度 \(O(n\log n)\)。
这里使用了标记永久化的思想,所以在查询过程中遇到的每个点都优可能对答案产生贡献。
#include<bits/stdc++.h>
#define db double
using namespace std;
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid (l+r>>1)
#define pdi pair<db,int>
#define mk make_pair
#define fi first
#define se second
const int N=4e5+5,M=4e4,INF=1e9+7;
const int mod1=39989,mod2=1e9;
const db eps=1e-10;
struct line{
db k,b;int l,r;
inline void init(int x1,int y1,int x2,int y2){
if(x1==x2)k=0,b=max(y1,y2),l=r=x1;
else{
k=1.0*(1.0*y2-y1)/(1.0*x2-x1);
b=1.0*y1-x1*k;l=min(x1,x2),r=max(x1,x2);
}
}
inline db get(int x){
if(l<=x&&x<=r)return k*x+b;
else return -INF;
}
}a[N];
int ans[N<<2];
inline pdi max(pdi a,pdi b){
if(a.fi-b.fi>eps)return a;
if(b.fi-a.fi>eps)return b;
return (a.se<b.se?a:b);
}
void update(int p,int l,int r,int L,int R,int x){
if(L<=l&&r<=R){
if(!ans[p])return ans[p]=x,void();
if(a[x].get(mid)-a[ans[p]].get(mid)>eps)swap(x,ans[p]);
if(a[x].get(l)-a[ans[p]].get(l)>eps||(a[x].get(l)==a[ans[p]].get(l)&&x<ans[p]))
update(ls(p),l,mid,L,R,x);
if(a[x].get(r)-a[ans[p]].get(r)>eps||(a[x].get(r)==a[ans[p]].get(r)&&x<ans[p]))
update(rs(p),mid+1,r,L,R,x);
return;
}
if(L<=mid)update(ls(p),l,mid,L,R,x);
if(mid<R)update(rs(p),mid+1,r,L,R,x);
}
pdi query(int p,int l,int r,int x){
pdi res=mk(-INF,0);
if(ans[p])res=mk(a[ans[p]].get(x),ans[p]);
if(l==r)return res;
if(x<=mid)res=max(res,query(ls(p),l,mid,x));
else res=max(res,query(rs(p),mid+1,r,x));
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n,lans=0,tot=0;cin>>n;
while(n--){
int op,x1,y1,x2,y2;cin>>op>>x1;
if(op==0){
x1=(x1+lans-1)%mod1+1;
cout<<(lans=query(1,1,M,x1).se)<<'\n';
}else{
cin>>y1>>x2>>y2;
x1=(x1+lans-1)%mod1+1,x2=(x2+lans-1)%mod1+1;
y1=(y1+lans-1)%mod2+1;y2=(y2+lans-1)%mod2+1;
++tot;a[tot].init(x1,y1,x2,y2);
update(1,1,M,min(x1,x2),max(x1,x2),tot);
}
}
return 0;
}
李超树经典应用是斜率优化,这里暂时不讲。
P4254 [JSOI2008] Blue Mary 开公司
就是李超树板子。

浙公网安备 33010602011771号