主席树

本文介绍了如何运用可持久化权值线段树(主席树)解决区间第k大问题、区间和查询以及树上点权值第k小问题。通过三道具体的编程竞赛题目,详细解析了主席树的应用,包括区间数值的累加、异或最大值以及树上点权值的查找。代码示例展示了如何实现主席树的数据结构和操作函数,帮助读者理解并掌握这一数据结构。
摘要由CSDN通过智能技术生成

主席树全称是可持久化权值线段树,主要解决区间第k大问题

注意空间大小为NlogN,一般可以开到N<<5

1.P4587 [FJOI2016]神秘数

【题意】给定一个数列,求最小的不能被l-r集合中的数表示的值

【分析】这个题目有一个很好的思想,类似于货币?

如果当前已经表示出1-pos,集合中最大的为mx,那么新加入的一个值一定在[mx+1,pos+1]才对结果又影响,如果小于mx+1,那么和以前一样,如果大于pos+1,中间有断开的位置,所以答案还是不会变,所以我们的问题变成了求l-r中范围在mx+1到pos+1的值的和,用主席树处理即可

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int n,m;
int a[maxn];
int root[maxn];
struct point
{
	int sum,l,r;
}tr[maxn*105];
int cnt;
void update(int &now,int l,int r,int val)
{
	tr[++cnt]=tr[now]; now=cnt; tr[cnt].sum+=val;
	if(l==r) return;
	int mid=l+r>>1;
	if(val<=mid) update(tr[now].l,l,mid,val);
	else update(tr[now].r,mid+1,r,val);
}
int query(int rx,int ry,int L,int R,int l,int r)
{
	if(!(tr[ry].sum-tr[rx].sum)) return 0;
	if(L>=l && R<=r) return tr[ry].sum-tr[rx].sum;
	int mid=L+R>>1;
	int res=0;
	if(l<=mid) res+=query(tr[rx].l,tr[ry].l,L,mid,l,r);
	if(mid<r) res+=query(tr[rx].r,tr[ry].r,mid+1,R,l,r);
	return res;
}
int main()
{
	freopen("mystery.in","r",stdin);
	freopen("mystery.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),root[i]=root[i-1],update(root[i],1,inf,a[i]);
	scanf("%d",&m);
	int x,y;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		int mx=0,pos=0;
		for(int sum;;)
		{
			sum=query(root[x-1],root[y],1,inf,mx+1,pos+1);
			if(!sum) break;
			mx=pos+1; pos+=sum;
		}
		printf("%d\n",pos+1);
	}
	return 0;
}

2.P3293 [SCOI2016]美味

【题意】给定一个数列a,给定一个l-r,对于每个b,x,l,r,求b xor (x+a[i]) (l<=i<=r)的最大值

【分析】这个异或的操作,明显让我们想到了从高位到低位枚举的贪心思想

对于每一位,如果b的第i位为0,那么最好的情况为第i位为1,i位之前的已经确定为ans,那么新的数的范围为ans-x到ans-x-(1<<k)-1,如果在这个范围内有值,那就可以使得第i位为1,

b的第i为1也是同理

在l-r求值域范围的和,用主席树维护即可

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int n,m,a[maxn];
int root[maxn];
struct segtree
{
	int l,r,sum;
}tr[maxn<<5];
int cnt;
void update(int &rt,int last,int L,int R,int val)
{
	if(val<L || val>R) return;
	rt=++cnt; tr[rt]=tr[last];	tr[rt].sum++;
	if(L==R) return;
	int mid=L+R>>1;
	update(tr[rt].l,tr[last].l,L,mid,val);
	update(tr[rt].r,tr[last].r,mid+1,R,val);
}
int query(int rt,int last,int L,int R,int l,int r)
{
	int sum=tr[rt].sum-tr[last].sum;
	if(r<L || l>R || !sum) return 0;
	if(L>=l && R<=r) return sum;
	int mid=L+R>>1;
	return query(tr[rt].l,tr[last].l,L,mid,l,r)+query(tr[rt].r,tr[last].r,mid+1,R,l,r);
}
int main()
{
	freopen("delicious.in","r",stdin);
	freopen("delicious.out","w",stdout);
	scanf("%d%d",&n,&m);
	int maxi=0;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),maxi=max(maxi,a[i]);
	int b,x,l,r;
	for(int i=1;i<=n;i++)
		update(root[i],root[i-1],0,maxi,a[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&b,&x,&l,&r);
		int ans=0,L,R,ok;
		for(int k=18;k>=0;k--)
		{
			int dig=b&(1<<k);
			if(dig && !query(root[l-1],root[r],0,maxi,ans-x,ans+(1<<k)-1-x)) ans+=(1<<k);
			if(!dig && query(root[l-1],root[r],0,maxi,ans+(1<<k)-x,ans-x+(1<<(k+1))-1)) ans+=(1<<k);
		}
		printf("%d\n",ans^b);
	}
	return 0;
}

3.P2633 Count on a tree

【题意】在线求树上两点间点权值的第k小

【分析】利用差分思想,把每个点到根路径的值放在一颗权值线段树里,进而构成了主席树

所以对于u,v,我们求出lca,查询的时候利用s[u]+s[b]-s[lca]-s[f[lca]]在树上进行查询第k小即可

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m,v[maxn],a[maxn],cnt;
int head[maxn],tot,root[maxn];
struct segtree
{
	int l,r,sum;
}tr[maxn<<5];
struct edge
{
	int to,nxt;
}e[maxn<<1];
void add(int x,int y)
{
	e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt;
}
int f[maxn][20],dep[maxn];
int ecnt;
void modify(int &rt,int last,int L,int R,int v)
{
	if(!rt) rt=++ecnt;
	if(L==R)
	{
		tr[rt].sum++;
		return;
	}
	int mid=L+R>>1;
	if(v<=mid) modify(tr[rt].l,tr[last].l,L,mid,v),tr[rt].r=tr[last].r;
	else modify(tr[rt].r,tr[last].r,mid+1,R,v),tr[rt].l=tr[last].l;
	tr[rt].sum=tr[tr[rt].l].sum+tr[tr[rt].r].sum;
}
int query(int x,int y,int c,int d,int L,int R,int val)
{
	if(L==R) return L;
	int mid=L+R>>1;
	int sum=tr[tr[x].l].sum+tr[tr[y].l].sum-tr[tr[c].l].sum-tr[tr[d].l].sum;
//	int sum=tr[x].sum+tr[y].sum-tr[c].sum-tr[d].sum;
	if(sum>=val) return query(tr[x].l,tr[y].l,tr[c].l,tr[d].l,L,mid,val);
	else return query(tr[x].r,tr[y].r,tr[c].r,tr[d].r,mid+1,R,val-sum);
}
void dfs(int x,int fa)
{
	f[x][0]=fa; dep[x]=dep[fa]+1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa) continue;
		modify(root[to],root[x],1,tot,a[to]);
		dfs(to,x);
	}
}
void lca_init()
{
	for(int k=1;k<=18;k++)
		for(int i=1;i<=n;i++)
			f[i][k]=f[f[i][k-1]][k-1];
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=18;i>=0;i--)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y) return x;
	for(int i=18;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main()
{
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&v[i]),a[i]=v[i];
	sort(v+1,v+n+1);
	tot=unique(v+1,v+n+1)-v;
	int x,y;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	} 
	for(int i=1;i<=n;i++)
		a[i]=lower_bound(v+1,v+tot+1,a[i])-v;
	modify(root[1],root[0],1,tot,a[1]);
	dfs(1,0); lca_init();
	int ans=0,z;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		x^=ans;
		int LCA=lca(x,y);
		ans=v[query(root[x],root[y],root[LCA],root[f[LCA][0]],1,tot,z)];
		printf("%d\n",ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值