动态开点线段树

使用场景

  1. \(4 \times n\) 开不下。
  2. 值域需要平移(有负数)。

什么时候开点

显然,访问的节点不存在时(只会在修改递归时开点)。

trick

区间里面有负数时,\(mid = (l + R - 1) / 2\)。

防止越界。

例如区间 \([-1,0]\)。

开点上限

考虑到 update 一次最多开 \(\log V\) 个点(最多递归 \(\log V\)次)。所以总空间应当开 \(O(m \log n)\)。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int tot;
int n,q;
const int maxn = 4e6+114;
struct Node{
int val, lt, rt, tag;
}tree[maxn];
void pushup(int &x){
tree[x].val=tree[tree[x].lt].val+tree[tree[x].rt].val;
}
void addtag(int &x,int l,int r,int v){
if(x==0){
x=++tot;
}
tree[x].val+=(r-l+1)*v;
tree[x].tag+=v;
}
void pushdown(int &x,int l,int r){
if(l>r) return ;
int mid=(l+r)/2;
addtag(tree[x].lt,l,mid,tree[x].tag);
addtag(tree[x].rt,mid+1,r,tree[x].tag);
tree[x].tag=0;
}
int ask(int &x,int lt,int rt,int l,int r){
if(rt<l||r<lt){
return 0;
}
if(l<=lt&&rt<=r){
return tree[x].val;
}
int mid=(lt+rt)/2;
pushdown(x,lt,rt);
int sum=0;
sum+=ask(tree[x].lt,lt,mid,l,r);
sum+=ask(tree[x].rt,mid+1,rt,l,r);
return sum;
}
void add(int &x,int lt,int rt,int l,int r,int v){
if(rt<l||r<lt){
return ;
}
if(l<=lt&&rt<=r){
addtag(x,lt,rt,v);
return ;
}
int mid=(lt+rt)/2;
pushdown(x,lt,rt);
add(tree[x].lt,lt,mid,l,r,v);
add(tree[x].rt,mid+1,rt,l,r,v);
pushup(x);
}
int root;
signed main(){
int n,q;
cin>>n>>q;
root=++tot;
for(int i=1;i<=n;i++){
int x;
cin>>x;
add(root,1,n,i,i,x);
}
for(int i=1;i<=q;i++){
int op;
cin>>op;
if(op==1){
int x,y,k;
cin>>x>>y>>k;
add(root,1,n,x,y,k);
}
else{
int x,y;
cin>>x>>y;
cout<<ask(root,1,n,x,y)<<'\n';
}
}
}

例题 1

题目传送门

化简题意得维护一个 01 区间,维护区间覆盖,取反以及查询第一个出现的 0

显然这个很鬼畜。

首先考虑怎么回答询问。

可以维护区间和,然后在线段树上二分。

然后考虑覆盖。

这个很显然可以维护一个覆盖标记。

那取反呢?

可以当取反和覆盖标记在同一节点时强制消除一个。

显然,取反就是让覆盖标记也取反。

那么就可以写出代码了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 4e6+1140;
const int inf = 1e18;
int tot;
struct Node{
long long lc,rc,val,tag1,tag2;
}tree[maxn];//val 表示区间中 1 的个数
void pushup(int x){
tree[x].val=tree[tree[x].lc].val+tree[tree[x].rc].val;
}
void addtag1(int &x,int lt,int rt,int tag)/*翻转*/{
if(x==0) x=++tot;
if(tag==0) return ;
if(tree[x].tag1==1){
tree[x].tag1=0;
tree[x].val=(rt-lt+1)-tree[x].val;
return ;
}
tree[x].tag1=1;
if(tree[x].tag2!=0){
tree[x].tag1=0;
tree[x].tag2=((tree[x].tag2-1)^1)+1;
tree[x].val=(tree[x].tag2-1)*(rt-lt+1);
return ;
}
tree[x].val=(rt-lt+1)-tree[x].val; return ;
}
void addtag2(int &x,int lt,int rt,int tag){
if(x==0) x=++tot;
if(tag==0) return ;
tree[x].tag1=0;
tree[x].val=(tag-1)*(rt-lt+1);
tree[x].tag2=tag;
//cout<<x<<' '<<lt<<' '<<rt<<'\n';
//cout<<lt<<' '<<rt<<' '<<tree[x].val<<'\n';
return ;
}
void pushdown(int x,int lt,int rt){
if(lt>=rt) return ;
int mid = (lt+rt-1)/2;
addtag1(tree[x].lc,lt,mid,tree[x].tag1);
addtag1(tree[x].rc,mid+1,rt,tree[x].tag1);
tree[x].tag1=0;
addtag2(tree[x].lc,lt,mid,tree[x].tag2);
addtag2(tree[x].rc,mid+1,rt,tree[x].tag2);
tree[x].tag2=0;
}
void reve(int &x,int l,int r,int lt,int rt){
if(r<lt||l>rt) return ;
if(r<=rt&&l>=lt){
addtag1(x,l,r,1);
return ;
}
int mid=(l+r-1)/2;
pushdown(x,l,r);
reve(tree[x].lc,l,mid,lt,rt);
reve(tree[x].rc,mid+1,r,lt,rt);
pushup(x);
}
void cover(int &x,int l,int r,int lt,int rt,int tag){
if(r<lt||l>rt) return ;
if(r<=rt&&l>=lt){
//cout<<"c:"<<l<<' '<<r<<'\n';
addtag2(x,l,r,tag);
return ;
}
int mid=(l+r-1)/2;
pushdown(x,l,r);
cover(tree[x].lc,l,mid,lt,rt,tag);
cover(tree[x].rc,mid+1,r,lt,rt,tag);
pushup(x);
}
int query(int &x,int l,int r){
if(l==r){
return l;
}
pushdown(x,l,r);
int mid = (l+r-1)/2;
if(tree[tree[x].lc].val<(mid-l+1)){
return query(tree[x].lc,l,mid);
}
else{
return query(tree[x].rc,mid+1,r);
}
}
int ask(int &x,int l,int r,int lt,int rt){
if(r<lt||l>rt) return 0;
if(r<=rt&&l>=lt) return tree[x].val;
int mid=(l+r-1)/2;
int sum=0;
pushdown(x,l,r);
sum+=ask(tree[x].lc,l,mid,lt,rt);
sum+=ask(tree[x].rc,mid+1,r,lt,rt);
return sum;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
inline void write(int x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); }
int n,q,root;
signed main(){
q=read();
n=inf;
root=1,tot=1;
while(q--){
int op;
op=read();
if(op==1){
int l,r;
l=read();
r=read();
cover(root,1,n,l,r,2);
}
else if(op==2){
int l,r;
l=read(),r=read();
cover(root,1,n,l,r,1);
}
else{
int l,r;
l=read(),r=read();
reve(root,1,n,l,r);
}
write(query(root,1,n));
putchar('\n');
}
return 0;
}

但是这样过不了,猜猜为什么?

线段树合并

在一个树形结构中每一个节点需要开一个权值线段树且区间范围完全一致)。

复杂度分析

一下分析建立在 树形结构合并 的前提下。

注意到在合并的时候需要递归 \(\log n\) 层当且仅仅当一棵线段树和另一棵线段树都有一个节点,并且合并完会变成一个节点,且把它的祖先节点也合并,也就是说每次花费 \(\log n\) 的代价合并了 \(\log n\) 个节点,由于最多有 \(n \log n\) 个节点,所以总复杂度就是 \(O(n \log n)\)。

CF600E

线段树记录最重的子树。然后合并答案。

现在就只有合并线段树的问题了。

trick

段树合并完后再还原需要额外空间,因此最好一次跑完答案,因此 线段树合并适合离线

实现(CF600E)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5+114;
const int inf = 1e5;
struct Node{
int ls,rs,val,cnt;// left son right son the anser the cnt
}tree[maxn * 20];
vector<int> edge[maxn];
int col[maxn];
int ans[maxn];
int root[maxn];
int tot;
inline void add(int u,int v){
edge[u].push_back(v);
edge[v].push_back(u);
}
void pushup(int &cur){
//cout<<tree[tree[cur].ls].cnt<<" "<<tree[tree[cur].ls].cnt<<'\n';
if(tree[tree[cur].ls].cnt<tree[tree[cur].rs].cnt){
tree[cur].cnt=tree[tree[cur].rs].cnt;
tree[cur].val=tree[tree[cur].rs].val;
}
else if(tree[tree[cur].rs].cnt<tree[tree[cur].ls].cnt){
tree[cur].cnt=tree[tree[cur].ls].cnt;
tree[cur].val=tree[tree[cur].ls].val;
}
else{
tree[cur].cnt=tree[tree[cur].ls].cnt;
tree[cur].val=tree[tree[cur].ls].val+tree[tree[cur].rs].val;
}
}
void addtag(int &cur,int lt,int rt,int l,int r,int v){
if(lt>r||rt<l) return ;
if(cur==0){
cur=++tot;
}
if(lt==rt){
tree[cur].cnt+=v;
tree[cur].val=lt;
return ;
}
int mid = (lt+rt)/2;
addtag(tree[cur].ls,lt,mid,l,r,v);
addtag(tree[cur].rs,mid+1,rt,l,r,v);
pushup(cur);
}
int merge(int a,int b,int l,int r){
if(a==0||b==0) return a+b;
if(l==r){
tree[a].cnt+=tree[b].cnt;
tree[a].val=l;
return a;
}
int mid=(l+r)/2;
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
void dfs(int now,int fa){
for(int nxt:edge[now]){
if(nxt==fa) continue;
dfs(nxt,now);
root[now]=merge(root[now],root[nxt],1,inf);
}
pushup(root[now]);
addtag(root[now],1,inf,col[now],col[now],1);
ans[now]=tree[root[now]].val;
}
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>col[i];
for(int i=2;i<=n;i++){
int u,v;
cin>>u>>v;
add(u,v);
}
dfs(1,0);
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
}

P4556

首先可以考虑树上差分。

然后显然我们只要处理桶合并的问题。

那么显然就可以线段树合并。

#include<bits/stdc++.h>
using namespace std;
const int inf = 2e5;
int n,q;
const int maxn = 2e5+114;
vector<int> Add[maxn*2],Del[maxn*2];
int ans[maxn];
int tot;
int root[maxn];
int fa[maxn][18];
int depth[maxn];
int lg[maxn];
vector<int> edge[maxn];
struct Node{
int ls,rs,val,cnt;// left son right son the anser the cnt
}tree[maxn * 20];
void pushup(int &cur){
//cout<<tree[tree[cur].ls].cnt<<" "<<tree[tree[cur].ls].cnt<<'\n';
if(tree[tree[cur].ls].cnt<tree[tree[cur].rs].cnt){
tree[cur].cnt=tree[tree[cur].rs].cnt;
tree[cur].val=tree[tree[cur].rs].val;
}
else if(tree[tree[cur].rs].cnt<tree[tree[cur].ls].cnt){
tree[cur].cnt=tree[tree[cur].ls].cnt;
tree[cur].val=tree[tree[cur].ls].val;
}
else{
tree[cur].cnt=tree[tree[cur].ls].cnt;
tree[cur].val=min(tree[tree[cur].ls].val,tree[tree[cur].rs].val);
}
}
void addtag(int &cur,int lt,int rt,int l,int r,int v){
if(lt>r||rt<l) return ;
if(cur==0){
cur=++tot;
}
if(lt==rt){
tree[cur].cnt+=v;
tree[cur].val=lt;
return ;
}
int mid = (lt+rt)/2;
addtag(tree[cur].ls,lt,mid,l,r,v);
addtag(tree[cur].rs,mid+1,rt,l,r,v);
pushup(cur);
}
int merge(int a,int b,int l,int r){
if(a==0||b==0) return a+b;
if(l==r){
tree[a].cnt+=tree[b].cnt;
tree[a].val=l;
return a;
}
int mid=(l+r)/2;
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
inline void add(int u,int v){
edge[u].push_back(v);
edge[v].push_back(u);
}
inline void dfs1(int now,int fath){
fa[now][0]=fath;
depth[now]=depth[fath] + 1;
for(int i=1;i<=lg[depth[now]];++i)
fa[now][i] = fa[fa[now][i-1]][i-1];
for(int nxt:edge[now]){
if(nxt==fath) continue;
dfs1(nxt,now);
}
}
int LCA(int x,int y){
if(depth[x] < depth[y])
swap(x, y);
while(depth[x] > depth[y])
x=fa[x][lg[depth[x]-depth[y]]- 1];
if(x==y)
return x;
for(int k=lg[depth[x]]-1; k>=0; --k)
if(fa[x][k] != fa[y][k])
x=fa[x][k],y=fa[y][k];
return fa[x][0];
}
void change(int u,int v,int z){
//cout<<u<<' '<<v<<' '<<z<<' '<<LCA(u,v)<<'\n';
Add[u].push_back(z);
Add[v].push_back(z);
int w=LCA(u,v);
Del[w].push_back(z);
Del[fa[w][0]].push_back(z);
}
void dfs2(int now,int fa){
for(int nxt:edge[now]){
if(nxt==fa) continue;
dfs2(nxt,now);
root[now]=merge(root[now],root[nxt],1,inf);
}
pushup(root[now]);
for(int c:Add[now]){
addtag(root[now],1,inf,c,c,1);
}
for(int c:Del[now]){
addtag(root[now],1,inf,c,c,-1);
}
ans[now]=tree[root[now]].val;
}
//树上差分打 add & del 标记,合并到某个节点再统一处理
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i = 1; i <= n; ++i)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v);
}
dfs1(1,0);
for(int i=1;i<=q;i++){
int u,v,z;
cin>>u>>v>>z;
change(u,v,z);
}
dfs2(1,0);
for(int i=1;i<=n;i++) cout<<ans[i]<<'\n';
}

P3521

考虑怎么求逆序对。

我们可以在合并的时候用 \(A_{1,mid} \times B_{mid+1,r}\) 来求出逆序对。

那么接下来就是一个板子了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5+114;
const int inf = 1e5;
struct Node{
int ls,rs,val;// left son right son the anser the cnt
}tree[maxn * 20];
int u,v,ans;
int tot;
int n;
void pushup(int &cur){
//cout<<tree[tree[cur].ls].cnt<<" "<<tree[tree[cur].ls].cnt<<'\n';
tree[cur].val=tree[tree[cur].ls].val+tree[tree[cur].rs].val;
}
int update(int l,int r,int val){
int pos=++tot;
tree[pos].val++;
if(l==r) return pos;
int mid=(l+r)>>1;
if(val<=mid) tree[pos].ls=update(l,mid,val);
else tree[pos].rs=update(mid+1,r,val);
return pos;
}
int merge(int a,int b,int l,int r){
if(a==0||b==0) return a+b;
if(l==r){
tree[a].val+=tree[b].val;
return a;
}
int mid=(l+r)/2;
u+=tree[tree[a].rs].val*tree[tree[b].ls].val;
v+=tree[tree[a].ls].val*tree[tree[b].rs].val;
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
int dfs(){
int root,U;
cin>>U;
if(U==0){
int lt=dfs(),rt=dfs();
u=0,v=0;
root=merge(lt,rt,1,n);
ans+=min(u,v);
//cout<<u<<' '<<v<<'\n';
return root;
}
else{
root=update(1,n,U);
return root;
}
}
signed main(){
cin>>n;
dfs();
cout<<ans;
}

P3605

只要维护子树最大值就可以了,用线段树合并即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+114;
vector<int> edge[maxn];
int val[maxn];
int ans[maxn];
int root[maxn];
const int inf = 1e9+10;
int n,tot;
struct Node{
int ls,rs,sum;// left son right son the anser the cnt
}tree[maxn * 20];
void pushup(int &cur){
tree[cur].sum=tree[tree[cur].ls].sum+tree[tree[cur].rs].sum;
}
int ask(int &cur,int lt,int rt,int l,int r){
if(rt<l||r<lt){
return 0;
}
if(l<=lt&&rt<=r){
return tree[cur].sum;
}
int mid=(lt+rt)/2;
int sum=0;
sum+=ask(tree[cur].ls,lt,mid,l,r);
sum+=ask(tree[cur].rs,mid+1,rt,l,r);
return sum;
}
void addtag(int &cur,int lt,int rt,int l,int r,int v){
if(lt>r||rt<l) return ;
if(cur==0){
cur=++tot;
}
if(lt==rt){
tree[cur].sum+=v;
return ;
}
int mid = (lt+rt)/2;
addtag(tree[cur].ls,lt,mid,l,r,v);
addtag(tree[cur].rs,mid+1,rt,l,r,v);
pushup(cur);
}
int merge(int a,int b,int l,int r){
if(a==0||b==0) return a+b;
if(l==r){
tree[a].sum+=tree[b].sum;
return a;
}
int mid=(l+r)/2;
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
void dfs(int u,int fa){
for(int v:edge[u]){
if(v==fa) continue;
dfs(v,u);
root[u]=merge(root[u],root[v],1,inf);
}
ans[u]=ask(root[u],1,inf,val[u]+1,inf);
addtag(root[u],1,inf,val[u],val[u],1);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>val[i];
for(int i=2;i<=n;i++){
int x;
cin>>x;
edge[x].push_back(i);
}
dfs(1,0);
for(int i=1;i<=n;i++) cout<<ans[i]<<'\n';
}

CF208E

本质上只需要维护 \(k\) 级祖先以及子树内深度为 \(x\) 的节点数量。

前者离线 dfs,后者线段树合并(下标表示深度)即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+114;
int num,a[N];
int dep[N];
vector<int> edge[N];
int root[N];
int ans[N];
vector< pair<int,int> > ask[N];//编号 :深度
vector<int> wyb;
int in[N];
struct Node{
int ls,rs;
int val;
}tree[N * 20];
int tot;
int n,q;
void pushup(int x){
tree[x].val=tree[tree[x].ls].val+tree[tree[x].rs].val;
}
void update(int &x,int l,int r,int pos,int v){
if(l>pos||r<pos) return ;
if(x==0){
x=++tot;
}
if(l==r&&l==pos){
tree[x].val+=v;
return ;
}
int mid=(l+r)/2;
update(tree[x].ls,l,mid,pos,v);
update(tree[x].rs,mid+1,r,pos,v);
pushup(x);
}
int query(int &x,int l,int r,int pos){
if(l>pos||r<pos){
return 0;
}
if(l==r&&l==pos){
return tree[x].val;
}
int mid=(l+r)/2,sum=0;
sum+=query(tree[x].ls,l,mid,pos);
sum+=query(tree[x].rs,mid+1,r,pos);
return sum;
}
int merge(int a,int b,int l,int r){
//cout<<a<<' '<<b<<' '<<l<<' '<<r<<'\n';
if(a==0||b==0){
//cout<<a<<' '<<b<<'\n';
return a+b;
}
if(l==r){
tree[a].val+=tree[b].val;
//cout<<tree[a].chifan.size()<<'\n';
tree[b].val=0;
return a;
}
int mid=(l+r)/2;
//cout<<tree[a].rs<<' '<<tree[b].rs<<'\n';
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
vector< pair<int,int> > ASK[N];//编号 :深度
void dfs(int cur,int fa){
wyb.push_back(cur);
dep[cur]=dep[fa]+1;
for(int u:edge[cur]){
if(u==fa) continue;
dfs(u,cur);
//cout<<cur<<' '<<root[cur]<<'\n';
root[cur]=merge(root[cur],root[u],1,n);
}
update(root[cur],1,n,dep[cur],1);
for(int i=0;i<ask[cur].size();i++){
int k=ask[cur][i].second;
if(k>=wyb.size()) continue;
int kfa=wyb[wyb.size()-k-1];
//cout<<cur<<' '<<k<<' '<<kfa<<' '<<dep[kfa]+k<<' '<<query(root[kfa],1,n,dep[kfa]+k)<<'\n';
ASK[kfa].push_back(make_pair(ask[cur][i].first,dep[kfa]+k));
/*
if(dep[cur]+ask[cur][i].second<=n){
//cout<<ask[cur][i].first<<' '<<query(root[cur],1,n,dep[cur]+ask[cur][i].second)<<'\n';
ans[ask[cur][i].first]=query(root[cur],1,n,dep[cur]+ask[cur][i].second);
}
*/
}
for(int i=0;i<ASK[cur].size();i++){
//cout<<cur<<' '<<ASK[cur][i].second<<' '<<query(root[cur],1,n,ASK[cur][i].second)<<'\n';
ans[ASK[cur][i].first]=query(root[cur],1,n,ASK[cur][i].second)-1;
}
wyb.pop_back();
}
inline void add(int u,int v){
edge[u].push_back(v);
edge[v].push_back(u);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(x==0) continue;
in[i]++;
add(x,i);
}
cin>>q;
for(int i=1;i<=q;i++){
int x,y;
cin>>x>>y;
ask[x].push_back(make_pair(i,y));
}
for(int i=1;i<=n;i++){
if(in[i]==0){
//cout<<i<<'\n';
dfs(i,0);
}
}
for(int i=1;i<=q;i++){
cout<<ans[i]<<' ';
} }

P3224

注意到所有连通块其实是在按树形结构合并。

所以对于每个连通块开一棵线段树。

合并操作就去合并两颗线段树。

查询操作就查询第 \(k\) 大即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5+114;
const int inf = 1e5;
struct Node{
int ls,rs,val,cnt;// left son right son the anser the cnt
}tree[maxn * 20];
vector<int> edge[maxn];
int fa[maxn];
int found(int x){
if(fa[x]==x) return x;
else return fa[x]=found(fa[x]);
}
int n,q;
int root[maxn];
int mp[maxn];
int tot;
void pushup(int &cur){
//cout<<tree[tree[cur].ls].cnt<<" "<<tree[tree[cur].ls].cnt<<'\n';
tree[cur].val=tree[tree[cur].ls].val+tree[tree[cur].rs].val;
}
int kth(int &cur,int l,int r,int k)
{
if(l==r) return l;
int mid=(l+r)/2;
if(tree[tree[cur].ls].val>=k){
return kth(tree[cur].ls,l,mid,k);
}
else{
return kth(tree[cur].rs,mid+1,r,k-tree[tree[cur].ls].val);
}
}
void addtag(int &cur,int lt,int rt,int l,int r,int v){
if(lt>r||rt<l) return ;
if(cur==0){
cur=++tot;
}
if(lt==rt){
tree[cur].val+=v;
return ;
}
int mid = (lt+rt)/2;
addtag(tree[cur].ls,lt,mid,l,r,v);
addtag(tree[cur].rs,mid+1,rt,l,r,v);
pushup(cur);
}
int merge(int a,int b,int l,int r){
if(a==0||b==0) return a+b;
if(l==r){
tree[a].val+=tree[b].val;
return a;
}
int mid=(l+r)/2;
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
return a;
}
int m;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
root[i]=++tot;
int u;
cin>>u;
mp[u]=i;
addtag(root[i],1,n,u,u,1);
}
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
x=found(x),y=found(y);
root[x]=merge(root[x],root[y],1,n);
fa[y]=x;
}
cin>>q;
for(int i=1;i<=q;i++){
char op;
cin>>op;
if(op=='B'){
int x,y;
cin>>x>>y;
x=found(x),y=found(y);
root[x]=merge(root[x],root[y],1,n);
fa[y]=x;
}
else{
int x,k;
cin>>x>>k;
x=found(x);
if(k>tree[root[x]].val){
cout<<"-1\n";
}
else{
cout<<mp[kth(root[x],1,n,k)]<<'\n';
}
}
}
}

P5384

本质上和 CF208E 没有区别。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+114;
int num,a[N];
int dep[N];
vector<int> edge[N];
int root[N];
int ans[N];
vector< pair<int,int> > ask[N];//编号 :深度
vector<int> wyb;
int in[N];
struct Node{
int ls,rs;
int val;
}tree[N * 4];
int tot;
int n,q;
stack<int> ioi;
void pushup(int x){
tree[x].val=tree[tree[x].ls].val+tree[tree[x].rs].val;
}
void update(int &x,int l,int r,int pos,int v){
if(l>pos||r<pos) return ;
if(x==0){
if(ioi.size()==0)
x=++tot;
else{
x=ioi.top();
ioi.pop();
}
}
if(l==r&&l==pos){
tree[x].val+=v;
return ;
}
int mid=(l+r)/2;
update(tree[x].ls,l,mid,pos,v);
update(tree[x].rs,mid+1,r,pos,v);
pushup(x);
}
int query(int &x,int l,int r,int pos){
if(l>pos||r<pos){
return 0;
}
if(l==r&&l==pos){
return tree[x].val;
}
int mid=(l+r)/2,sum=0;
sum+=query(tree[x].ls,l,mid,pos);
sum+=query(tree[x].rs,mid+1,r,pos);
return sum;
}
int merge(int a,int b,int l,int r){
//cout<<a<<' '<<b<<' '<<l<<' '<<r<<'\n';
if(a==0||b==0){
//cout<<a<<' '<<b<<'\n';
return a+b;
}
if(l==r){
tree[a].val+=tree[b].val;
//cout<<tree[a].chifan.size()<<'\n';
tree[b].val=0;
//ioi.push(b);
return a;
}
int mid=(l+r)/2;
//cout<<tree[a].rs<<' '<<tree[b].rs<<'\n';
tree[a].ls=merge(tree[a].ls,tree[b].ls,l,mid);
tree[a].rs=merge(tree[a].rs,tree[b].rs,mid+1,r);
pushup(a);
ioi.push(b);
return a;
}
vector< pair<int,int> > ASK[N];//编号 :深度
void dfs(int cur,int fa){
wyb.push_back(cur);
dep[cur]=dep[fa]+1;
for(int u:edge[cur]){
if(u==fa) continue;
dfs(u,cur);
//cout<<cur<<' '<<root[cur]<<'\n';
root[cur]=merge(root[cur],root[u],1,n);
}
update(root[cur],1,n,dep[cur],1);
for(int i=0;i<ask[cur].size();i++){
int k=ask[cur][i].second;
if(k>=wyb.size()) continue;
int kfa=wyb[wyb.size()-k-1];
//cout<<cur<<' '<<k<<' '<<kfa<<' '<<dep[kfa]+k<<' '<<query(root[kfa],1,n,dep[kfa]+k)<<'\n';
ASK[kfa].push_back(make_pair(ask[cur][i].first,dep[kfa]+k));
/*
if(dep[cur]+ask[cur][i].second<=n){
//cout<<ask[cur][i].first<<' '<<query(root[cur],1,n,dep[cur]+ask[cur][i].second)<<'\n';
ans[ask[cur][i].first]=query(root[cur],1,n,dep[cur]+ask[cur][i].second);
}
*/
}
for(int i=0;i<ASK[cur].size();i++){
//cout<<cur<<' '<<ASK[cur][i].second<<' '<<query(root[cur],1,n,ASK[cur][i].second)<<'\n';
ans[ASK[cur][i].first]=query(root[cur],1,n,ASK[cur][i].second)-1;
}
wyb.pop_back();
}
inline void add(int u,int v){
edge[u].push_back(v);
edge[v].push_back(u);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=2;i<=n;i++){
int x;
cin>>x;
if(x==0) continue;
in[i]++;
add(x,i);
} for(int i=1;i<=q;i++){
int x,y;
cin>>x>>y;
ask[x].push_back(make_pair(i,y));
}
for(int i=1;i<=n;i++){
if(in[i]==0){
//cout<<i<<'\n';
dfs(i,0);
}
}
for(int i=1;i<=q;i++){
cout<<ans[i]<<' ';
} }

动态开点线段树&线段树合并学习笔记的更多相关文章

  1. HDU 6464.免费送气球-动态开点-权值线段树(序列中第first小至第second小的数值之和)(感觉就是只有一个状态的主席树) (“字节跳动-文远知行杯”广东工业大学第十四届程序设计竞赛)

    免费送气球 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submi ...

  2. [CSP-S模拟测试]:表格(动态开点二维线段树+离散化)

    题目传送门(内部题112) 输入格式 一个数$N$,表示矩形的个数. 接下来$N$行,每行四个整数$X_a,Y_a,X_b,Y_b$.分别表示每个矩形左下角和右上角的坐标. 保证$(X_a<X_ ...

  3. 线段树合并学习笔记(P4556)

    直入主题: 学习线段树合并..... 从名字就能看出,这个东西要合并线段树..... 线段树怎么能合并呢...... 暴力合就行了啊...... 一次从上往下的遍历,把所有的节点信息暴力合并,然后就没 ...

  4. [NOIP2015模拟10.27] [JZOJ4270] 魔道研究 解题报告(动态开点+权值线段树上二分)

    Description “我希望能使用更多的魔法.不对,是预定能使用啦.最终我要被大家称呼为大魔法使.为此我决定不惜一切努力.”——<The Grimoire of Marisa>雾雨魔理 ...

  5. 左偏树 / 非旋转treap学习笔记

    背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...

  6. KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态获取 《寒江独钓》内核学习笔记(5)

    目录 . 相关阅读材料 . <加密与解密3> . [经典文章翻译]A_Crash_Course_on_the_Depths_of_Win32_Structured_Exception_Ha ...

  7. P2617 Dynamic Rankings (动态开点权值线段树 + 树状数组)

    题意:带修求区间k小 题解:回忆在使用主席树求区间k小时 利用前缀和的思想 既然是前缀和 那么我们可以使用更擅长维护前缀和的树状数组 但是这里每一颗权值线段树就不是带版本的 而是维护数组里i号点的权值 ...

  8. 【动态树问题】LCT学习笔记

    我居然还不会LCT QAQ真是太弱了 必须学LCT QAQ ------------------线割分是我www------------ LinkCut-Tree是基于Splay(由于Splay能够非 ...

  9. BZOJ 1036: [ZJOI2008]树的统计Count [树链剖分]【学习笔记】

    1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 14302  Solved: 5779[Submit ...

  10. 树链剖分 树剖求lca 学习笔记

    树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...

随机推荐

  1. Vue获取DOM的几种方法

    虽然Vue实现了MVVM模型,将数据和表现进行了分离,我们只需要更新数据就能使DOM同步更新,但是某些情况下,还是需要获取DOM元素进行操作(比如引入的某个库要求传入一个根dom元素作为根节点,或者写 ...

  2. 使用类的习题(c++ prime plus)

    第一题 vect.h: #ifndef VECTOR_H_ #define VECTOR_H_ #include <iostream> namespace VECTOR { class V ...

  3. 当jar包执行时,内嵌的文件找不到时,可以这样解决!

    1.加载是可以加载到的,但是只能是以流的形式存在. 2.如果要按文件进行加载,可以新建一个文件,然后以流的形式写入到新的文件中. 3.加载这个新的文件来进行处理.

  4. 三大常用集群leader选举+哨兵模式原理

    一,Zookeeper集群的leader选举 Zookeeper的选举机制两个触发条件:集群启动阶段和集群运行阶段leader挂机(这2种场景下选举的流程基本一致) 1,Zookeeper集群中的fo ...

  5. pwm 理解

    PWM:  假设PWM的时钟主频是 PWM_CLK_FREQ Hz,则如果需要输出频率为 xHz,占空比为 y% 的波形时, 则只需要在定时器的周期寄存器中写入(PWM_CLK_FREQ / x),在 ...

  6. Phpstorm 最新永久激活教程

    使用ja-netfilter激活Jetbrains系列软件 注意:无限试用脚本已经失效.本教程适合2021.3.*之上的高版本,使用ja-netfilter插件进行激活操作,永久有效 激活步骤: 第一 ...

  7. 水印 canvas 实现

    let str = info; let c = document.createElement("canvas"); document.body.appendChild.c; let ...

  8. 【随笔】Java处理异常输出对象Exception,转为String输出

    声明:这段代码也是从网上摘抄的,当时忘记记录地址了,此为转载,勿怪 public static String handleException(Exception e) { StringBuffer m ...

  9. 前端复习之Ajax,忘完了

    1 * Day01: 2 * Ajax 3 * Asynchronous JavaScript and XML 4 * 直译中文 - JavaScript和XML的异步 5 * (不严格的定义)客户端 ...

  10. Oracle表主键作为外键都用在哪些表查询

    Oracle中,如果设置了外键,删除数据时,必须将外键关联一并删除,但是如果对项目不是很熟悉时,我们无法判断到底都在哪些表中有外键关联,以下提供了一个查询的SQL,可以通过数据库查询,查找到所有的外键 ...