[学习笔记]主席树经典例题

level 1

level 1-1 模板-查询k小值

模板,不再赘述。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 200000
int n,Q;
int a[MAXN+5],b[MAXN+5];
int root[MAXN+5];
int rnum;
struct node
{
    int lc,rc;
    int sum;
}tree[MAXN*20+5];
void Build(int &x,int l,int r)
{
    x=++rnum;
    if(l==r)return ;
    int mid=(l+r)>>1;
    Build(tree[x].lc,l,mid);
    Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos)
{
    int Nr=++rnum;
    tree[Nr]=tree[x];
    tree[Nr].sum=tree[x].sum+1;
    if(l==r)return Nr;
    int mid=(l+r)>>1;
    if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos);
    else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos);
    return Nr;
}
int Query(int Lr,int Rr,int l,int r,int val)
{
    int Pv=tree[tree[Rr].lc].sum-tree[tree[Lr].lc].sum;
    if(l==r)return l;
    int mid=(l+r)>>1;
    if(val<=Pv)return Query(tree[Lr].lc,tree[Rr].lc,l,mid,val);
    else return Query(tree[Lr].rc,tree[Rr].rc,mid+1,r,val-Pv);
}
int main()
{
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++)
    {scanf("%d",&a[i]);b[i]=a[i];}
    sort(b+1,b+n+1);
    int p=unique(b+1,b+n+1)-b-1;
    Build(root[0],1,p);
    for(int i=1;i<=n;i++)
    {
        int P=lower_bound(b+1,b+p+1,a[i])-b;
        root[i]=Insert(root[i-1],1,p,P);
    }
    while(Q--)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[Query(root[l-1],root[r],1,p,k)]);
    }
}

level 1-2 [POI2014]couriers

给一个数列,每次询问一个区间内有没有一个数出现次数超过一半
n≤500000n≤500000n500000

nnn个版本的权值线段树,每个点统计数值iii的出现次数。
询问的时候用rrr版本减去l−1l-1l1版本,维护sum递归查找一下即可。
[代码算了…]

level 2

level 2-1 [CQOI2015]任务查询系统

最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。

其实这道题本身是不用主席树的。
关键是它要强制在线…
一个十分显然的套路:
运用差分思想,将操作拆成两个部分:在Si位置插入线段树,在Ei+1位置从线段树中删除。
线段树的每个位置代表优先值为iii的计算机。在线段树上维护优先值之和。
通过主席树保存每个时间的线段树即可。
由于有优先值很大,因此要离散化。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 300000
int n,Q;
int root[MAXN*20+5];
int rnum;
struct As
{
    int l,r,val;
}a[MAXN+5];
struct Qry
{
    int pos,val,st,Tp;
}qry[MAXN+5];
struct node
{
    int lc,rc;
    long long cnt,sum;
}tree[MAXN*20+5];
int Hs[MAXN+5];
bool cmp(Qry s1,Qry s2){return s1.Tp<s2.Tp;}
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
int Insert(int x,int l,int r,int pos,int val,int F)
{
    int Nr=++rnum;
    tree[Nr]=tree[x];
    tree[Nr].cnt+=F;
    tree[Nr].sum+=F*val;
    if(l==r)return Nr;
    int mid=(l+r)>>1;
    if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos,val,F);
    else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos,val,F);
    return Nr;
}
long long Query(int x,int l,int r,int val)
{
    if(l==r)return val*tree[x].sum/tree[x].cnt;
    int mid=(l+r)>>1;
    int Pv=tree[tree[x].lc].cnt;
    if(tree[x].cnt<=val)return tree[x].sum;
    if(Pv>=val)return Query(tree[x].lc,l,mid,val);
    return Query(tree[x].rc,mid+1,r,val-Pv)+tree[tree[x].lc].sum;
}
int main()
{
    n=read(),Q=read();
    for(int i=1;i<=n;i++)
    {
        a[i].l=read(),a[i].r=read(),a[i].val=read();
        Hs[i]=a[i].val;
    }
    sort(Hs+1,Hs+n+1);
    int p=unique(Hs+1,Hs+n+1)-Hs-1;
    for(int i=1;i<=n;i++)
    {
        int P=lower_bound(Hs+1,Hs+p+1,a[i].val)-Hs;
        qry[i*2-1]=(Qry){P,a[i].val,1,a[i].l};
        qry[i*2]=(Qry){P,a[i].val,-1,a[i].r+1};
    }
    sort(qry+1,qry+n*2+1,cmp);
    int Qcnt=1;
    for(int i=1;i<=Q;i++)
    {
        root[i]=root[i-1];
        while(i==qry[Qcnt].Tp)
        {
            root[i]=Insert(root[i],1,p,qry[Qcnt].pos,qry[Qcnt].val,qry[Qcnt].st);
            Qcnt++;
        }
    }
    long long ans=1;
    while(Q--)
    {
        int ps,A,B,C;
        long long k;
        scanf("%d%d%d%d",&ps,&A,&B,&C);
        k=(ans*A+B)%C+1;
        ans=Query(root[ps],1,p,k);
        printf("%lld\n",ans);
    }
}

level 2-2 可持久化并查集

用主席树维护fa数组和dep数组。
将每次合并看做是单点修改fa值。
注意这里并不能用路径压缩,因为路径压缩修改的fa值非常多,很容易MLE。
这里可以用启发式合并,使得深度大约在log⁡n\log nlogn左右
由于find的时候要Query,因此还会多一个log⁡\loglog
复杂度O(nlog⁡2n)O(n\log^2 n)O(nlog2n)
由于实际深度多数小于log⁡n\log nlogn因此常数较小。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 200000
int tp,n,Q,u,v;
int rt[MAXN*40+5];
struct Per_DSU{
	int L[MAXN*40+5],R[MAXN*40+5],fa[MAXN*40+5],dep[MAXN*40+5];
	int ecnt;
	Per_DSU(){ecnt=0;}
	void Build(int &p,int l,int r)
	{
		p=++ecnt;
		if(l==r){fa[p]=l;return ;}
		int mid=(l+r)>>1;
		Build(L[p],l,mid),Build(R[p],mid+1,r);
	}
	void merge(int p1,int &p2,int l,int r,int pos,int v)
	{
		p2=++ecnt;L[p2]=L[p1],R[p2]=R[p1];
		if(l==r){fa[p2]=v;dep[p2]=dep[p1];return ;}
		int mid=(l+r)>>1;
		if(pos<=mid)merge(L[p1],L[p2],l,mid,pos,v);
		else merge(R[p1],R[p2],mid+1,r,pos,v);
	}
	void update(int p,int l,int r,int pos)
	{
		if(l==r){dep[p]++;return ;}
		int mid=(l+r)>>1;
		if(pos<=mid)update(L[p],l,mid,pos);
		else update(R[p],mid+1,r,pos);
	}
	int Query(int p,int l,int r,int pos)
	{
		if(l==r)return p;
		int mid=(l+r)>>1;
		if(pos<=mid)return Query(L[p],l,mid,pos);
		else return Query(R[p],mid+1,r,pos);
	}
	int xfind(int rt,int pos)
	{
		int np=Query(rt,1,n,pos);
		if(fa[np]==pos)return np;
		return xfind(rt,fa[np]);
	}
}S;
int main()
{
	scanf("%d%d",&n,&Q);
	S.Build(rt[0],1,n);
	for(int i=1;i<=Q;i++)
	{
		scanf("%d%d",&tp,&u);
		if(tp==1)
		{
			scanf("%d",&v);
			int nx,ny;
			rt[i]=rt[i-1];
            nx=S.xfind(rt[i],u);ny=S.xfind(rt[i],v);
            if(S.fa[nx]!=S.fa[ny])
            {
                if(S.dep[nx]>S.dep[ny])swap(nx,ny);
                S.merge(rt[i-1],rt[i],1,n,S.fa[nx],S.fa[ny]);
                if(S.dep[nx]==S.dep[ny])S.update(rt[i],1,n,S.fa[ny]);
            }
		}
		if(tp==2)rt[i]=rt[u];
		if(tp==3){
			scanf("%d",&v);
			int nx,ny;
			rt[i]=rt[i-1];
            nx=S.xfind(rt[i],u);ny=S.xfind(rt[i],v);
			if(S.fa[nx]==S.fa[ny])puts("1");
            else puts("0");
		}
	}
}

level 2-3 [SDOI2010]粟粟的书架

其实这题可以不用线段树做…
Task1:
由于Pi,j≤1,000,R,C≤200P_{i,j}≤1,000,R, C≤200Pi,j1,000,R,C200
考虑直接矩阵前缀和。
定义cnt[i][j][k]cnt[i][j][k]cnt[i][j][k](1,1)−(i,j)(1,1)-(i,j)(1,1)(i,j)矩阵中数值大于等于kkk的个数。
sum[i][j][k]sum[i][j][k]sum[i][j][k](1,1)−(i,j)(1,1)-(i,j)(1,1)(i,j)矩阵中数值大于等于kkk的数值和。
二分一下kkk即可
Task2:
考虑使用主席树。
做法很显然,基本上和模板一样。
发现也有用莫队做的,但实际上这道题用莫队并不好…

level 3

level 3-1 [CTSC2018]混合果汁

看到CTSC被吓到了…
其实是一道联赛签到题。

看到最小值最大第一直觉二分ddd
考虑果汁按ddd排序。
题目要求价格乘以容量最大,由于总容量固定,价格肯定是越小越好,那么肯定还要按价格排序。
这是二维的,由于价格只用前缀,明显是一个主席树可以解决的问题。
具体做法就是先让果汁按ddd从大到小排序,依次加入主席树中,这样我们就有了nnn个版本的线段树,接下来二分ddd,在相应版本的线段树上再次二分即可。
注意这题要开long long…
O(nlog⁡2n)O(n \log^2 n)O(nlog2n)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;
#define MAXN 100000
#define LL long long
#define INF 1000000000000000001
#define MOD 1000000000
#define PB push_back
#define MP make_pair
#define FR first
#define SE second
int n,Q,tp1,tp2,tp3;
int rt[MAXN+5];
int pcnt;
struct juice
{
	int d,p,l;
}S[MAXN+5];
struct node
{
    int lc,rc;
    LL cnt,sum;
}tree[MAXN*20+5];
bool cmp(juice s1,juice s2){return s1.d>s2.d;}
void Build(int &x,int l,int r)
{
    x=++pcnt;
    if(l==r)return ;
    int mid=(l+r)>>1;
    Build(tree[x].lc,l,mid);
    Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos,LL val)
{
    int Nr=++pcnt;
    tree[Nr]=tree[x];
    tree[Nr].sum=tree[x].sum+1LL*val*pos;
    tree[Nr].cnt=tree[x].cnt+val;
    if(l==r)return Nr;
    int mid=(l+r)>>1;
    if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos,val);
    else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos,val);
    return Nr;
}
LL Query(int x,int l,int r,LL L)
{
	//printf("%d %lld->%d %d\n",tree[x].cnt,L,l,r);
	if(tree[x].cnt<L)return INF;
    if(l==r)return 1LL*L*l;
    int mid=(l+r)>>1;
    if(tree[tree[x].lc].cnt>=L)return Query(tree[x].lc,l,mid,L);
    else return tree[tree[x].lc].sum+Query(tree[x].rc,mid+1,r,L-tree[tree[x].lc].cnt);
}
int main()
{
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d%d%d",&tp1,&tp2,&tp3);
    	S[i]=(juice){tp1,tp2,tp3};
	}
    sort(S+1,S+n+1,cmp);
    Build(rt[0],1,MAXN);
    for(int i=1;i<=n;i++)
    rt[i]=Insert(rt[i-1],1,MAXN,S[i].p,S[i].l);
    while(Q--)
    {
        LL G,L;
        scanf("%lld%lld",&G,&L);
        int l=1,r=n,p=0;
        while(l<=r)
        {
        	int mid=(l+r)>>1;
        	if(Query(rt[mid],1,MAXN,L)<=G)p=mid,r=mid-1;
        	else l=mid+1;
    	}
    	printf("%d\n",(p!=0)?S[p].d:-1);
    }

}

level 3-2 欧铂瑞特

写了一篇blog

仍在更新中

转载于:https://www.cnblogs.com/Panda-hu/p/11145743.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值