就是将一颗权值线段树的中间变化过程保存下来
朴素的想法就是保存中间每颗树(但这样空间会炸),但仔细想想插入的过程发现每次只有一条长为logn的链改变了,所以我们只需要保存那个链并将其连在原树上即可这样就保存了中间所有变化过程的树(合理利用公共资源)节点的编号也不是之前二叉树编号规则了,这些地方就很像动态开点线段树(很有意思的一个东西,可以开拓思路用)
此处将我学习的链接贴一下:https://www.bilibili.com/video/BV1ib41137Ry?from=search&seid=14598533944342614266
还有主席树的图文讲解,可以自己搜博客看看,理解了思想再看代码,并多想想为什么这么写
bibi上的UP主讲的(哔哩哔哩上很多学习资源的,自己多学学呀)
最早的板子题就不放了,(因为我膨胀了)
2020多校牛客第五场H-Interval
定义个这
n个数q次询问每次给你个L和R求 set S(l,r)大小,(会去重的)
每次答案与上次答案有关(所以别想莫队了)
考虑固定左端点,最多会得到logn个右端点,有这些区间ans就+1
这就是典型的二维偏序问题,L按秩排序,把L相同的放在主席树同一区间段记录每个L的有边界,然后L到R中小于R的区间段数就是小于R的区间求和就完事了(题解压根就没写这句,这就是著名的众所周知的知识)
但是这没法去重,所以考虑值相同的L1,R1, L2,R2 再加一个L1,R2的权值为-1的区间段然后再二维偏序就完事了
是不是好简单,代码也没写很久,也没改很久就出来了,可是赛场上你想的到么
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
typedef long long ll;
#define int ll
int n,q;
vector<int>son[N];
vector<int>wei[30];
vector<pair<int,int>>v[N*30];
ll ppp=pow(2,30)-1;
int a[N];
int rr[N];
struct node
{
int ls,rs,sum;
} tree[N*200];
int root[N*200];
int cnt;
int cnt__;
void init();
void update(int l,int r,int &x,int y,int pos,int k)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{
tree[++cnt]=tree[y];
tree[cnt].sum+=k;
x=cnt;
if(l==r)
return;
int mid=(l+r)/2;
if(pos<=mid)
update(l,mid,tree[x].ls,tree[y].ls,pos,k);
else
update(mid+1,r,tree[x].rs,tree[y].rs,pos,k);
}
int query(int l,int r,int x,int y,int k)///前缀和思想
{
if(l==r)
return tree[y].sum-tree[x].sum;
int mid=(l+r)/2;
if(k<=mid)
return query(l,mid,tree[x].ls,tree[y].ls,k);
else
return tree[tree[y].ls].sum-tree[tree[x].ls].sum+query(mid+1,r,tree[x].rs,tree[y].rs,k);
}
void solve()
{
scanf("%lld",&q);
int ans=0;
while(q--)
{
int L,R;
scanf("%lld%lld",&L,&R);
L=(L^ans)%n+1;
R=(R^ans)%n+1;
if(L>R)swap(L,R);
ans=query(1,n,root[rr[L-1]],root[rr[R]],R);
printf("%lld\n",ans);
}
}
signed main()
{
init();
solve();
}
void init()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<30;i++)///枚举二进制数
{
for(int j=1;j<=n;j++)
{
if((a[j]&(1<<i))==0)
wei[i].push_back(j);
}
}
for(int i=1;i<=n;i++)
{
son[i].push_back(i);
for(int j=0;j<30;j++)
{
int x=lower_bound(wei[j].begin(),wei[j].end(),i)-wei[j].begin();
if(x==wei[j].size()||i==wei[j][x]) continue;
son[i].push_back(wei[j][x]);
}
}
for(int i=1;i<=n;i++)
{
sort(son[i].begin(),son[i].end());
}
map<int,int>mp;
for(int i=1;i<=n;i++)
{
ll cad=ppp;
for(int j=0;j<son[i].size();j++)
{
cad&=a[son[i][j]];
if(!mp[cad])
{
v[++cnt__].push_back(make_pair(i,son[i][j]));
mp[cad]=cnt__;
}
else
v[mp[cad]].push_back(make_pair(i,son[i][j]));
// cout<<cad<<'?'<<endl;
}
}
//for(int i=0;i<=10;i++)cout<<i<<':'<<mp[i]<<endl;
//cout<<"???"<<endl;
for(int i=1;i<=cnt__;i++)
sort(v[i].begin(),v[i].end());
for(int i=1;i<=cnt__;i++)
{
for(int j=0;j<v[i].size()-1;j++)
{
int LL=v[i][j].first;
int RR=v[i][j+1].second;
son[LL].push_back(-RR);
}
}
int d=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<son[i].size();j++)
{
//cout<<i<<' '<<son[i][j]<<endl;
if(son[i][j]>0)
update(1,n,root[d],root[d-1],son[i][j],1);
else
update(1,n,root[d],root[d-1],-son[i][j],-1);
d++;
}
rr[i]=d-1;
}
// cout<<d<<endl;
}
牛客主席树练习题——树的距离
题目链接:https://ac.nowcoder.com/acm/problem/14415
题目描述
wyf非常喜欢树。一棵有根数树上有N个节点,1号点是他的根,每条边都有一个距离,而wyf是个爱问奇怪问题的熊孩子,他想知道对于某个点x,以x为根的子树上,所有与x距离大于等于k的点与x的距离之和。
输入描述:
第一行一个正整数N
接下来N-1描述这棵树,每行两个数第i行两个数p和D表示树上有一条p到i+1长度为D的边。(p<=i)
下面一行一个正整数Q表示wyf的询问次数。
接下来Q行每行两个正整数x和k。 (1<=N,Q<=2x105,1<=D,K<=106)
输出描述:
对于每次询问x,k输出以x为根的子树上,所有与x距离大于等于k的点与x的距离之和。(若不存在这样的点,则输出应为0)
按树上dfs序建主席树,先儿子们后父亲,x子树为root[x]-root[zuo[x]-1],主席树上维护离根节点距离,区间和,离x大于等于k=离1大于等于dep[x]+k,然后搜个区间和就完事了(说是这么说,然而我3个小时了还没撸出代码)
留份错误的代码吧(有机会一定写)如同去年的网络流那样,今年才有时间去写
我A了 找到了bug后 过了95%的数据,然后对拍,发现边界处理有些问题,然后过了
虽然它只是个简单主席树板子题,虽然看起来只是一小步,却是我学习主席树算法道路上的一大步
#include<bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);
typedef long long ll;
const ll mod =998244353;
const ll N=2e5+7;
#define FOPEN freopen("C:\\Users\\l\\P3381_8.in","r",stdin)
void init();
ll n,m,cnt,a[N],root[N],k;
ll t;
ll NO[N];
struct kkk
{
ll to,w;
};
struct node
{
ll ls,rs,num,sum;
} tree[N*50];
void update(ll l,ll r,ll &x,ll y,ll pos);///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
ll query(ll l,ll r,ll x,ll y,ll k,ll xx);///前缀和思想
ll zuo[N];
vector<kkk>son[N];
vector<ll>v,xu;
ll dep[N];
void dfs(ll x,ll fa)
{
if(son[x].size()==1) zuo[x]=x;
for(ll i=0;i<son[x].size();i++)
{
auto d=son[x][i];
if(d.to==fa) continue;
dep[d.to]=dep[x]+d.w;
dfs(d.to,x);
}
if(!zuo[fa]) zuo[fa]=zuo[x];
v.push_back(dep[x]);
NO[x]=xu.size();
xu.push_back(x);
}
ll getid(ll x)///用于离散化
{
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void solve()
{
ll q;scanf("%lld",&q);
while(q--)
{
ll x,k;
scanf("%lld%lld",&x,&k);
k+=dep[x];
if(k>v[v.size()-1]) cout<<0<<endl;
else
cout<<query(1,n,root[NO[zuo[x]]-1],root[NO[x]],getid(k),x)<<endl;
}
}
signed main()
{
init();
//for(ll i=1;i<=n;i++) cout<<i<<':'<<root[i]<<endl;
solve();
}
void init()
{
scanf("%lld",&n);
for(ll i=2;i<=n;i++)
{
ll to,w;
scanf("%lld%lld",&to,&w);
son[i].push_back({to,w});
son[to].push_back({i,w});
}
son[1].push_back({0,0}); xu.push_back(0);
dfs(1,0);
//for(int i=1;i<=n;i++) cout<<zuo[i]<<' ';cout<<endl;
//for(int i=1;i<=n;i++) cout<<NO[i]<<' ';cout<<endl;
//for(int i=1;i<=n;i++) cout<<xu[i]<<' ';cout<<endl;
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(ll i=1;i<=n;i++)
{
update(1,n,root[i],root[i-1],getid(dep[xu[i]]));
}
}
void update(ll l,ll r,ll &x,ll y,ll pos)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{
tree[++cnt]=tree[y];
tree[cnt].sum+=v[pos-1];
tree[cnt].num++;
x=cnt;
if(l==r)
return;
ll mid=(l+r)/2;
if(pos<=mid)
update(l,mid,tree[x].ls,tree[y].ls,pos);
else
update(mid+1,r,tree[x].rs,tree[y].rs,pos);
}
ll query(ll l,ll r,ll x,ll y,ll k,ll xx)///前缀和思想
{
ll ans=0;
if(l==r)
return tree[y].sum-tree[x].sum-(tree[y].num-tree[x].num)*dep[xx];
ll mid=(l+r)/2;
if(k<=mid)
ans+=query(l,mid,tree[x].ls,tree[y].ls,k,xx),ans+=tree[tree[y].rs].sum-tree[tree[x].rs].sum-(tree[tree[y].rs].num-tree[tree[x].rs].num)*dep[xx];
else
ans+=query(mid+1,r,tree[x].rs,tree[y].rs,k,xx);
return ans;
}
/*
5
1 76
2 75
2 67
4 59
5
4 67
2 62
4 89
6 37
3 42
*/
/
更新,主席树求区间mex 权值主席树(啊哈,本来理解的就是对的,传参传错了)
按时间戳每个叶子节点存放该点出现的最新事件,维护区间最小值,二分1-n查找R节点树 如果该区间最小值小于L 说明该区间有值没在L-R中出现,它就是mex
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
typedef long long ll;
//#define int ll
int a[N];
struct node
{
int ls,rs,mi;
} tree[N*200];
int root[N];
int cnt;
void init();
void update(int l,int r,int &x,int y,int pos,int k)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{
tree[++cnt]=tree[y];
//tree[cnt].mi=max(tree[cnt].mi,k);
x=cnt;
if(l==r)
{
tree[cnt].mi=k;
return;
}
int mid=(l+r)/2;
if(pos<=mid)
update(l,mid,tree[x].ls,tree[y].ls,pos,k);
else
update(mid+1,r,tree[x].rs,tree[y].rs,pos,k);
tree[x].mi=min(tree[tree[x].ls].mi,tree[tree[x].rs].mi);
}
int query(int l,int r,int x,int k)///前缀和思想
{
int mid=(l+r)/2;
if(l==r) return l;
if(tree[tree[x].ls].mi<k)
return query(l,mid,tree[x].ls,k);
else
return query(mid+1,r,tree[x].rs,k);
}
void dfs(int l,int r,int x)
{
cout<<l<<' '<<r<<' '<<tree[x].mi<<endl;
if(l==r) return;
int mid=(l+r)/2;
dfs(l,mid,tree[x].ls);
dfs(mid+1,r,tree[x].rs);
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
update(0,n,root[i],root[i-1],x,i);
//dfs(0,n,root[i]);
}
while(m--)
{
int a1,a2;
cin>>a1>>a2;
cout<<query(0,n,root[a2],a1)<<endl;
}
}
signed main()
{
ios::sync_with_stdio(false);
init();
solve();
}
void init()
{
}