• 「刷题笔记」主席树


    模板题

    LG3919 「模板」可持久化线段树 1(可持久化数组)

    这是一道模板题,需要支持以下操作:

    • 在某个历史版本上修改某一个位置上的值
    • 访问某个历史版本上的某一位置的值
      为了访问历史版本,暴力的方法是直接存储,但是在(n,m leq 1e6)的情况下肯定是跑不过去的,这个时候就需要可持久化
      主席树是实现可持久化的一个很好的选择

    主席树的实现和动态开点线段树有些相似,因为更新版本是一个动态的过程,普通的线段树的编号访问是无法做到的
    我们发现每次如果修改一个数,那么只有线段树根节点到这个数所在节点路径上的权值发生了变化,所以每次更新时,我们就从根节点往需更改的叶子节点走,在走的过程中,将经过的节点复制一份,再更新一下,并存下当前版本的树根编号
    更详细的讲解请至洛谷题解区

    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll int
    #define ull unsigned long long
    #define N 1000005
    
    ll n,m;
    
    #define ls tr[rt].l
    #define rs tr[rt].r
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    ll a[N];
    struct vert
    {
    	ll l,r,val;
    }tr[N*20];
    ll cnt=0;
    
    void clone(ll &a)
    {
    	tr[++cnt]=tr[a];
    	a=cnt;
    }
    
    void build(ll &rt,ll l,ll r)
    {
    	if(!rt)rt=++cnt;
    	if(l==r)
    	{
    		tr[rt].val=a[l];
    		return;
    	}
    	ll m=(l+r)>>1;
    	build(lson);
    	build(rson);
    }
    
    void update(ll &rt,ll l,ll r,ll x,ll v)
    {
    	clone(rt);
    	if(l==r)
    	{
    		tr[rt].val=v;
    		return;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)update(lson,x,v);
    	else if(m<x)update(rson,x,v);
    }
    
    ll query(ll rt,ll l,ll r,ll x)
    {
    	if(l==r)return tr[rt].val;
    	ll m=(l+r)>>1;
    	if(m>=x)return query(lson,x);
    	else if(m<x)return query(rson,x);
    }
    
    void print(ll rt,ll l,ll r)
    {
    	if(l==r)
    	{
    		cout<<tr[rt].val<<' ';
    		return;
    	}
    	ll m=(l+r)>>1;
    	print(lson);
    	print(rson);
    }
    
    ll root[N];
    ll ver,t,loc,val;
    
    inline ll read(){
        ll s=0,f=0;char c=getchar();
        while(c<'0'||c>'9') f=(c=='-'),c=getchar();
        while(c>='0'&&c<='9') s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
        return f?-s:s;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)a[i]=read();
    	build(root[0],1,n);
    	for(int i=1;i<=m;i++)
    	{
    		ver=read(),t=read(),loc=read();
    		root[i]=root[ver];
    		if(t==1)
    		{
    			val=read();
    			update(root[i],1,n,loc,val);
    		}
    		else if(t==2)
    		{
    			printf("%d
    ",query(root[ver],1,n,loc));
    		}
    	}
    	//for(int i=0;i<=m;i++)print(root[i],1,n),puts("");
    	return 0;
    }
    

    define ll int有奇效

    LG3834 「模板」可持久化线段树 2(主席树)

    这道题就是主席树更为常见的用法:查询区间第(k)
    题中所给的数列是静态的,那可持久化怎么用呢?
    考虑求(k)小的过程:在这个区间的权值线段树上走,因此,我们需要得到一个区间的权值线段树
    因此,我们可以利用前缀和思想,给长度为(n)的数列建立一个有(n)个根的主席树,以第(i)个根为根的树代表(a[1]~a[i])中每个数出现的次数
    (不知道能不能说是把权值线段树可持久化……)
    每次查询([l,r])的时候从两个根节点(r),(l-1)同步向下走,每次两个节点的权值差即为这段区间的权值,将它与(k)比较,决定向左走还是向右走
    (向右走时,记得用(k)减去一个左边的区间权值和)

    需要注意的是,查询(k)大的题中一般数据范围会很大,常常是需要离散化的,之后就不提这一点了

    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 200005
    
    ll n,m;
    
    #define ls tr[rt].l
    #define rs tr[rt].r
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*20];
    ll cnt=0;
    
    void up(ll rt)
    {
    	tr[rt].val=tr[ls].val+tr[rs].val;
    }
    
    void clone(ll &a)
    {
    	tr[++cnt]=tr[a];
    	a=cnt;
    }
    
    void build(ll &rt,ll l,ll r)
    {
    	if(!rt)rt=++cnt;
    	if(l==r)
    	{
    		tr[rt].val=0;
    		return;
    	}
    	ll m=(l+r)>>1;
    	build(lson);
    	build(rson);
    	up(rt);
    }
    
    void update(ll &rt,ll l,ll r,ll x)
    {
    	clone(rt);
    	if(l==r)
    	{
    		tr[rt].val++;
    		return;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)update(lson,x);
    	if(m<x)update(rson,x);
    	up(rt);
    }
    
    ll query(ll r1,ll r2,ll l,ll r,ll k)
    {
    	if(l==r)
    	{
    		return l;
    	}
    	ll m=(l+r)>>1,tmp=tr[tr[r2].l].val-tr[tr[r1].l].val;
    	if(tmp>=k)return query(tr[r1].l,tr[r2].l,l,m,k);
    	if(tmp<k)return query(tr[r1].r,tr[r2].r,m+1,r,k-tmp);//注意!
    }
    
    ll root[N];
    ll a[N],b[N];
    ll l,r,c;
    
    int main()
    {
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),b[i]=a[i];
    	sort(b+1,b+n+1);
    	ll tot=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;//cout<<a[i]<<' ';cout<<endl;
    	build(root[0],1,tot);
    	for(int i=1;i<=n;i++)root[i]=root[i-1],update(root[i],1,tot,a[i]);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%lld%lld%lld",&l,&r,&c);
    		printf("%lld
    ",b[query(root[l-1],root[r],1,tot,c)]);
    	}
    	return 0;
    }
    

    常见题型

    静态区间k小

    SP3946 MKTHNUM - K-th Number

    其实就是刚刚的模板题……双倍经验

    动态区间k小

    LG2617 Dynamic Rankings

    首先考虑在之前主席树维护前缀和的基础上暴力更改,这时的修改会影响所有之后的前缀和,一不小心就(O(n^2))
    但是前缀和是肯定要维护的,还有什么其他的方式吗?

    窝会树状数组!

    用树状数组维护前缀和时,只需要更改(log)个点,这样就能做到(O(nlog n))修改了,快乐
    那么具体的做法是把树状数组魔改一下下,以前树状数组的每个节点都给他在主席树上开一个根节点,每次修改和查询时,先按树状数组的套路处理出会用到的点,然后正常修改查询就好了,这里其实就是相当于树状数组的每一个节点是一棵权值线段树
    其实似乎结合代码会更好理解……

    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 1000005
    
    ll n,m;
    ll a[N],b[N];
    
    #define ls tr[rt].l
    #define rs tr[rt].r
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*40];
    ll cnt=0;
    
    void update(ll &rt,ll l,ll r,ll x,ll v)
    {
    	if(!rt)rt=++cnt;
    	tr[rt].val+=v;
    	if(l==r)return;
    	ll m=(l+r)>>1;
    	if(m>=x)update(lson,x,v);
    	else update(rson,x,v);
    }
    
    inline ll lb(ll x){return x&-x;} 
    
    ll root[N];
    ll tot=0;
    void upd(ll pos,ll val)
    {
    	ll k=lower_bound(b+1,b+tot+1,a[pos])-b;
    	for(int i=pos;i<=n;i+=lb(i))update(root[i],1,tot,k,val);
    }
    
    ll cn[2];
    ll tmp[2][20];
    
    struct que
    {
    	char opt[3];
    	ll t1,t2,t3; 
    }qq[N];
    
    ll query(ll l,ll r,ll k)
    {
    	if(l==r)return l;
    	ll sum=0,m=(l+r)>>1;
    	for(int i=1;i<=cn[1];i++)sum+=tr[tr[tmp[1][i]].l].val;
    	for(int i=1;i<=cn[0];i++)sum-=tr[tr[tmp[0][i]].l].val;
    	if(sum>=k)
    	{
    		for(int i=1;i<=cn[1];i++)tmp[1][i]=tr[tmp[1][i]].l;
    		for(int i=1;i<=cn[0];i++)tmp[0][i]=tr[tmp[0][i]].l;
    		return query(l,m,k);
    	}
    	else 
    	{
    		for(int i=1;i<=cn[1];i++)tmp[1][i]=tr[tmp[1][i]].r;
    		for(int i=1;i<=cn[0];i++)tmp[0][i]=tr[tmp[0][i]].r;
    		return query(m+1,r,k-sum);
    	}
    }
    
    int main()
    {
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),b[++tot]=a[i];
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%s",qq[i].opt);
    		if(qq[i].opt[0]=='C')scanf("%lld%lld",&qq[i].t1,&qq[i].t2),b[++tot]=qq[i].t2;
    		else scanf("%lld%lld%lld",&qq[i].t1,&qq[i].t2,&qq[i].t3);
    	}
    	sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
    	for(int i=1;i<=n;i++)upd(i,1);
    	for(int i=1;i<=m;i++)
    	{
    		if(qq[i].opt[0]=='C')
    		{
    			upd(qq[i].t1,-1);
    			a[qq[i].t1]=qq[i].t2;
    			upd(qq[i].t1,1);
    		}
    		else 
    		{
    			memset(tmp,0,sizeof tmp);cn[0]=cn[1]=0;
    			for(int j=qq[i].t1-1;j;j-=lb(j))tmp[0][++cn[0]]=root[j];
    			for(int j=qq[i].t2;j;j-=lb(j))tmp[1][++cn[1]]=root[j];
    			printf("%lld
    ",b[query(1,tot,qq[i].t3)]);
    		}
    	}
    	return 0;
    }
    

    求区间内不同数字个数

    LG1972 [SDOI2009]HH的项链

    设对于每个数字(i),数列中上一个和他相同的数字的位置为(lst[i])
    则题意可以简化为:求区间([l,r])内所有满足(lst[i] < l)(i)的个数
    考虑一下,当(i)对答案有贡献时,(lst[i])对答案是肯定没有贡献的
    因此,可以用主席树维护前缀和,每次将(a[i])的权值+1,并将(lst[i])的权值-1
    查询([l,r])的答案时,使用第(r)个版本,再查询([l,r])的权值和

    然而这题其实可以直接树状数组维护的,主席树似乎被洛谷卡掉了qaq

    code:

    #pragma GCC optimize("O3")
    #include<bits/stdc++.h>
    using namespace std;
    #define ll int
    #define ull unsigned long long
    #define N 1000005
    #define M 200005
    
    #define ls tr[rt].l
    #define rs tr[rt].r
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    ll n,m;
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*25];
    ll cnt=0;
    
    void up(ll rt)
    {
    	tr[rt].val=tr[ls].val+tr[rs].val;
    }
    
    void clone(ll &a)
    {
    	tr[++cnt]=tr[a];
    	a=cnt;
    }
    
    void build(ll &rt,ll l,ll r)
    {
    	if(!rt)rt=++cnt;
    	if(l==r)
    	{
    		tr[rt].val=0;
    		return;
    	}
    	ll m=(l+r)>>1;
    	build(lson);
    	build(rson);
    }
    
    void update(ll &rt,ll l,ll r,ll x,ll v)
    {
    	clone(rt);tr[rt].val+=v;
    	if(l==r)
    	{
    		return;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)update(lson,x,v);
    	else if(m<x)update(rson,x,v);
    }
    
    ll query(ll rt,ll l,ll r,ll nl,ll nr)
    {
    	if(nl<=l&&r<=nr)
    	{
    		return tr[rt].val;
    	}
    	ll m=(l+r)>>1;
    	ll res=0;
    	if(m>=nl)res+=query(lson,nl,nr);
    	if(m<nr)res+=query(rson,nl,nr);
    	return res;
    }
    
    ll a[N],b[N];
    ll root[N];
    ll t1,t2;
    ll la[N];
    
    inline ll read(){
        ll s=0,f=0;char c=getchar();
        while(c<'0'||c>'9') f=(c=='-'),c=getchar();
        while(c>='0'&&c<='9') s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
        return f?-s:s;
    }
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)a[i]=read();
    	build(root[0],1,n);
    	for(int i=1;i<=n;i++)
    	{
    		if(!la[a[i]])
    		{
    			root[i]=root[i-1],update(root[i],1,n,i,1);
    			la[a[i]]=i;
    		}
    		else
    		{
    			ll tmp=root[i-1];update(tmp,1,n,la[a[i]],-1);
    			root[i]=tmp,update(root[i],1,n,i,1);
    			la[a[i]]=i;
    		}
    	}
    	scanf("%d",&m);
    	for(int i=1;i<=m;i++)
    	{
    		t1=read(),t2=read();
    		printf("%d
    ",query(root[t2],1,n,t1,t2));
    	}
    	return 0;
    }
    

    与差分结合

    P3168 [CQOI2015]任务查询系统

    (m)个权值,分别在([l_i,r_i])时间内出现
    每次询问时间(t)时从小到大排序的前(k)个权值的和

    暴力肯定爆炸,考虑差分
    这样就把每个区间的操作转化成了两个单点操作
    然后把单点操作按时间排序,主席树做一下前缀和,得到的就是每个时刻的树
    线段树维护几个标记,权值和,个数和,叶子节点维护这个叶子代表的数字,防止有重复数字时出现错误
    优先级范围很大,需要离散化
    注意当(k)大于当前时间总任务数时直接输出所有权值和

    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 1000005
    
    ll m,n;
    ll s[N],e[N],p[N],x,a,b,c,k;
    ll tmp[N],tot=0;
    ll pre=1;
    
    struct qq
    {
    	ll op,ti,num;
    }cz[N];
    ll qcn=0;//1+2-
    bool cmp(qq a,qq b)
    {
    	return a.ti<b.ti;
    }
    
    #define ls tr[rt].l
    #define rs tr[rt].r
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    struct vert
    {
    	ll l,r,cn,id,val;
    }tr[N*50];
    ll cnt=0;
    
    void clone(ll &rt)
    {
    	tr[++cnt]=tr[rt];
    	rt=cnt;
    }
    
    void up(ll rt)
    {
    	tr[rt].cn=tr[ls].cn+tr[rs].cn;
    	tr[rt].val=tr[ls].val+tr[rs].val;
    }
    
    ll root[N];
    
    void build(ll &rt,ll l,ll r)
    {
    	if(!rt)rt=++cnt;
    	if(l==r)
    	{
    		tr[rt].cn=tr[rt].val=0;
    		return;
    	}
    	ll m=(l+r)>>1;
    	build(lson);
    	build(rson);
    	up(rt);
    }
    
    void upd(ll &rt,ll l,ll r,ll x,ll v)
    {
    	clone(rt);
    	if(l==r)
    	{
    		tr[rt].cn+=v;
    		tr[rt].val+=v*tmp[x];
    		tr[rt].id=tmp[x];
    		//cout<<' '<<x<<' '<<tr[rt].cn<<' '<<tr[rt].val<<endl;
    		return;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)upd(lson,x,v);
    	else upd(rson,x,v);
    	up(rt);
    }
    
    ll query(ll rt,ll l,ll r,ll k)
    {
    	if(l==r)
    	{
    		//cout<<' '<<tr[rt].val<<endl;
    		return tr[rt].id*k;
    	}
    	ll tt=tr[ls].cn;
    	ll m=(l+r)>>1;
    	//cout<<'	'<<tt<<endl;
    	if(tt>=k)
    	{
    		//cout<<' '<<query(lson,k)<<endl;
    		return query(lson,k);
    	}
    	else
    	{	
    		//cout<<' '<<query(rson,k-tt)<<' '<<tr[ls].val<<endl;
    		return query(rson,k-tt)+tr[ls].val;
    	}
    }
    
    int main()
    {
    	scanf("%lld%lld",&m,&n);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%lld%lld%lld",&s[i],&e[i],&p[i]);
    		tmp[++tot]=p[i];
    	}
    	sort(tmp+1,tmp+tot+1);
    	tot=unique(tmp+1,tmp+tot+1)-tmp-1;
    	for(int i=1;i<=m;i++)
    	{
    		p[i]=lower_bound(tmp+1,tmp+tot+1,p[i])-tmp;
    		cz[++qcn].op=1,cz[qcn].ti=s[i],cz[qcn].num=p[i];
    		cz[++qcn].op=2,cz[qcn].ti=e[i]+1,cz[qcn].num=p[i];
    	}
    	sort(cz+1,cz+qcn+1,cmp);
    	//for(int i=1;i<=qcn;i++)cout<<cz[i].op<<' '<<cz[i].ti<<' '<<cz[i].num<<endl;
    	build(root[0],1,tot);
    	ll now=1;
    	for(int i=1;i<=qcn;i++)
    	{
    		while(now<=cz[i].ti)
    		{
    			root[now]=root[now-1];
    			now++;
    		}	
    		if(cz[i].op==1)upd(root[cz[i].ti],1,tot,cz[i].num,1);
    		else upd(root[cz[i].ti],1,tot,cz[i].num,-1);
    	}
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld%lld%lld%lld",&x,&a,&b,&c);
    		k=(a*pre+b)%c+1;
    		//cout<<pre<<' '<<k<<' '<<x<<' '<<root[x]<<endl;
    		if(k<=tr[root[x]].cn)pre=query(root[x],1,tot,k);
    		else pre=tr[root[x]].val;
    		printf("%lld
    ",pre);
    	}
    	return 0;
    }
    

    两个属性的区间和

    HDU5140 Hun Gui Wei 公司

    两个属性在主席树上,我们可以把其中一个属性离散后用作主席树的版本号,按这个思路做另一个属性的前缀和
    两个属性都是(pm 1e17)的,需要都离散一下
    注意(lower\_bound)(upper\_bound)的区别:

    • (lower\_bound)用于求排序数组中(geq a)的第一个数的位置
    • (upper\_bound)用于求排序数组中(ge a)的第一个数的位置

    如果要把一个区间([a,b])的左右端点离散,则离散后的区间应该是([l\_b(a),u\_b(b)-1])
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 100005
    
    ll n,m;
    ll b[N],tot=0;
    ll c[N],to=0;
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*50];
    ll cnt=0;
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    void clone(ll &rt)
    {
    	tr[++cnt]=tr[rt];
    	rt=cnt;
    }
    
    void build(ll &rt,ll l,ll r)
    {
    	if(!rt)rt=++cnt;
    	if(l==r)
    	{
    		tr[rt].val=0;
    		return;
    	}
    	ll m=(l+r)>>1;
    	build(lson);
    	build(rson);
    }
    
    void ins(ll &rt,ll l,ll r,ll x,ll v)
    {
    	clone(rt);
    	tr[rt].val+=v;
    	if(l==r)return;
    	ll m=(l+r)>>1;
    	if(m>=x)ins(lson,x,v);
    	else ins(rson,x,v);
    }
    
    ll query(ll rt,ll l,ll r,ll nl,ll nr)
    {
    	if(nl<=l&&r<=nr)return tr[rt].val;
    	ll m=(l+r)>>1;
    	ll res=0;
    	if(m>=nl)res+=query(lson,nl,nr);
    	if(m<nr)res+=query(rson,nl,nr);
    	return res;
    }
    
    void print(ll rt,ll l,ll r)
    {
    	if(l==r)
    	{
    		cout<<tr[rt].val<<' ';
    		return;
    	}
    	ll m=(l+r)>>1;
    	print(lson);
    	print(rson);
    }
    
    struct node
    {
    	ll s,l,a;
    }t[N];
    
    bool cmp(node a,node b)
    {
    	return a.l<b.l;
    }
    
    ll mx=0;
    ll k=0;
    ll t1,t2,t3,t4;
    ll root[N];
    
    void solve()
    {
    	k=0;
    	cnt=0;
    	tot=to=0;
    	memset(tr,0,sizeof tr);
    	memset(t,0,sizeof t);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld%lld%lld",&t[i].s,&t[i].l,&t[i].a);
    		b[++tot]=t[i].l;
    		c[++to]=t[i].a;
    	}
    	sort(t+1,t+n+1,cmp);
    	sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
    	sort(c+1,c+to+1);to=unique(c+1,c+to+1)-c-1;
    	for(int i=1;i<=n;i++)t[i].l=lower_bound(b+1,b+tot+1,t[i].l)-b;
    	for(int i=1;i<=n;i++)t[i].a=lower_bound(c+1,c+to+1,t[i].a)-c;
    	//for(int i=1;i<=n;i++)
    	//{
    	//	cout<<'	'<<t[i].l<<' '<<t[i].a<<' '<<t[i].s<<endl;
    	//}
    	//for(int i=1;i<=10;i++)cout<<lower_bound(b+1,b+tot+1,i)-b<<' ';cout<<endl;
    	//for(int i=1;i<=10;i++)cout<<upper_bound(b+1,b+tot+1,i)-b<<' ';cout<<endl;
    	build(root[0],1,to);
    	for(int i=1,j=1;j<=n;i++)
    	{
    		root[i]=root[i-1];
    		//cout<<i<<' '<<j<<' ';
    		do
    		{
    			ins(root[i],1,to,t[j].a,t[j].s);
    			j++;
    		}while(t[j].l==t[j-1].l);
    		//cout<<j-1<<endl;
    		//print(root[i],1,to);cout<<endl;
    	}
    	scanf("%lld",&m);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%lld%lld%lld%lld",&t1,&t2,&t3,&t4);
    		t1+=k;t2-=k;t3+=k;t4-=k;
    		if(t1>t2)swap(t1,t2);
    		if(t3>t4)swap(t3,t4);
    		//cout<<' '<<t1<<' '<<t2<<' '<<t3<<' '<<t4<<endl;
    		t1=lower_bound(b+1,b+tot+1,t1)-b;
    		t2=upper_bound(b+1,b+tot+1,t2)-b-1;
    		t3=lower_bound(c+1,c+to+1,t3)-c;
    		t4=upper_bound(c+1,c+to+1,t4)-c-1;
    		//cout<<' '<<t1<<' '<<t2<<' '<<t3<<' '<<t4<<endl;
    		k=query(root[t2],1,to,t3,t4)-query(root[t1-1],1,to,t3,t4);
    		printf("%lld
    ",k);
    	}
    }
    
    int main()
    {
    	while(~scanf("%lld",&n))solve();
    	return 0;
    }
    

    验证某串是否为给定区间子串

    花神的嘲讽计划

    求某串是否为数列某一长度为(k)区间的子串。
    预处理:把每个长度为(k)的字串([i,i+k-1])求出(hash)值,用版本(i+k-1)记录(将这个值的次数++)
    询问:对于询问([a,b]),先求所给(k)长度区间的(hash)值,问题就转化成了版本(a+k-1)到版本(b)有没有新出现的这个(hash)
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 100005
    
    ll n,m,k;
    
    const ll mod=192608170817;
    const ll base=17;
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*50];
    ll cnt=0;
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    void clone(ll &rt)
    {
    	tr[++cnt]=tr[rt];
    	rt=cnt;
    }
    
    void ins(ll &rt,ll l,ll r,ll x)
    {
    	clone(rt);
    	tr[rt].val++;
    	if(l==r)
    	{	
    		//cout<<l<<endl;
    		return;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)ins(lson,x);
    	else ins(rson,x);
    }
    
    bool query(ll r1,ll r2,ll l,ll r,ll x)
    {
    	if(!r2)return 0;
    	if(l==r)
    	{
    		//cout<<l<<' '<<tr[r2].val<<' '<<tr[r1].val<<endl;
    		return tr[r2].val>tr[r1].val;
    	}
    	ll m=(l+r)>>1;
    	if(m>=x)return query(tr[r1].l,tr[r2].l,l,m,x);
    	else return query(tr[r1].r,tr[r2].r,m+1,r,x);
    }
    
    ll a[N];
    ll root[N];
    ll t1,t2;
    
    int main()
    {
    	scanf("%lld%lld%lld",&n,&m,&k);
    	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    	for(int i=1;i<=n-k+1;i++)
    	{
    		ll tmp=0;
    		for(int j=0;j<k;j++)tmp=(tmp*base+a[i+j])%mod;
    		root[i+k-1]=root[i+k-2];
    		ins(root[i+k-1],1,mod,tmp+1);
    		//cout<<i+k-1<<' '<<tmp<<endl;
    	}
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%lld%lld",&t1,&t2);
    		ll tmp=0,tt;
    		for(int i=1;i<=k;i++)scanf("%lld",&tt),tmp=(tmp*base+tt)%mod;
    		//cout<<tmp<<endl;
    		if(query(root[t1+k-2],root[t2],1,mod,tmp+1))puts("No");
    		else puts("Yes");
    	}
    	return 0;
    }
    

    树上k大

    森林

    主要就是一个(lca)的操作,和之前雨天的尾巴那个题有些类似的地方
    静态是很好做的,而这题是动态的
    那么每次合并就是一个(nlog n)的操作
    我们记录每个块的大小,然后把小的合并到大的上面显然是更优的
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll int
    #define ull unsigned long long
    #define N 800005
    #define E 800005
    
    ll test;
    ll n,m,t;
    char opt[3];
    ll t1,t2,t3;
    
    struct edge
    {
    	ll u,v;
    }e[E];
    ll tot=0,head[E],next[E];
    void add(ll &a,ll &b)
    {
    	++tot;
    	e[tot].u=a;
    	e[tot].v=b;
    	next[tot]=head[a];
    	head[a]=tot;
    }
    
    ll v[N];
    ll b[N],lt=0;
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*60];
    ll cnt=0;
    ll root[N];
    
    inline void ins(ll pre,ll &rt,ll l,ll r,ll x)
    {
    	tr[rt=++cnt]=tr[pre];
    	tr[rt].val++;
    	if(l==r)return;
    	ll m=(l+r)>>1;
    	if(m>=x)ins(tr[pre].l,lson,x);
    	else ins(tr[pre].r,rson,x);
    }
    /*
    ll lg[N];
    void lgp()
    {
    	lg[1]=0;
    	for(int i=2;i<=N-5;i++)lg[i]=lg[i-1]+(i==(2<<lg[i>>1]));
    	//for(int i=1;i<=30;i++)cout<<lg[i]<<endl;
    }*/
    
    ll dep[N],fa[N][18],siz[N];
    ll col[N];
    
    inline ll find(ll x)
    {
    	if(col[x]==x)return x;
    	else return col[x]=find(col[x]);
    }
    
    inline void dfs(ll u,ll f,ll rt)
    {
    	//memset(fa[u],0,sizeof fa[u]);
    	fa[u][0]=f;
    	col[u]=col[f];
    	dep[u]=dep[f]+1;
    	siz[rt]++;
    	//root[u]=root[f];
    	ins(root[f],root[u],1,lt,v[u]);
    	//memset(fa[u],0,sizeof fa[u]);
    	for(int i=1;i<=16;i++)
    	{
    		fa[u][i]=fa[fa[u][i-1]][i-1];
    	}
    	for(int i=head[u];i;i=next[i])
    	{
    		ll v=e[i].v;
    		if(v==f)continue;
    		dfs(v,u,rt);
    	}
    }
    
    inline ll lca(ll a,ll b)
    {
    	if(a==b)return a;
    	if(dep[a]<dep[b])swap(a,b);
    	ll d=dep[a]-dep[b];
    	for(int i=16;i>=0;i--)
    	{
    		if(d&(1<<i))a=fa[a][i];
    	}
    	//while(dep[a]>dep[b])a=fa[a][lg[dep[a]-dep[b]]];
    	if(a==b)return a;
    	for(int i=16;i>=0;i--)
    	{
    		if(fa[a][i]!=fa[b][i])
    		{
    			a=fa[a][i];
    			b=fa[b][i];
    		}
    	}
    	return fa[a][0];
    }
    
    inline ll query(ll r1,ll r2,ll lc,ll flc,ll l,ll r,ll k)
    {
    	ll tt,m;
    	while(l<r)
    	{
    		tt=tr[tr[r1].l].val+tr[tr[r2].l].val-tr[tr[lc].l].val-tr[tr[flc].l].val;
    		//cout<<l<<' '<<r<<' '<<k<<' '<<tt<<endl;
    		ll m=(l+r)>>1;
    		if(k<=tt)
    		{
    			r1=tr[r1].l; r2=tr[r2].l; lc=tr[lc].l; flc=tr[flc].l;
    			r=m;
    		}
    		else 
    		{
    			k-=tt;
    			r1=tr[r1].r; r2=tr[r2].r; lc=tr[lc].r; flc=tr[flc].r;
    			l=m+1;
    		}
    	}
    	return b[l];
    }
    inline ll read(){
        ll s=0,f=0;char c=getchar();
        while(c<'0'||c>'9') f=(c=='-'),c=getchar();
        while(c>='0'&&c<='9') s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
        return f?-s:s;
    }
    
    
    inline void init()
    {	
    	tot=cnt=lt=0;
    	scanf("%d%d%d",&n,&m,&t);
    	for(int i=1;i<=n;i++)v[i]=read(),b[++lt]=v[i],col[i]=i;
    	for(int i=1;i<=m;i++)t1=read(),t2=read(),add(t1,t2),add(t2,t1);
    	sort(b+1,b+lt+1); lt=unique(b+1,b+lt+1)-b-1;
    	for(int i=1;i<=n;i++)v[i]=lower_bound(b+1,b+lt+1,v[i])-b;
    	for(int i=1;i<=n;i++)if(col[i]==i)dfs(i,0,i);
    }
    
    
    ll lst=0;
    
    int main()
    {
    	//lgp();
    	//printf("%lld
    ",sizeof(tr)>>20);
    	scanf("%d",&test);
    	
    		init();
    		for(int i=1;i<=t;i++)
    		{
    			scanf("%s",opt);
    			if(opt[0]=='Q')
    			{
    				t1=read(),t2=read(),t3=read();
    				t1^=lst; t2^=lst; t3^=lst;
    				lst=query(root[t1],root[t2],root[lca(t1,t2)],root[fa[lca(t1,t2)][0]],1,lt,t3);
    				printf("%d
    ",lst);
    			}
    			else if(opt[0]=='L')
    			{
    				t1=read(),t2=read();
    				t1^=lst; t2^=lst;
    				//cout<<t1<<' '<<t2<<endl;
    				add(t1,t2); add(t2,t1);
    				ll f1=find(t1),f2=find(t2);
    				if(siz[f1]>siz[f2])dfs(t2,t1,f1);
    				else dfs(t1,t2,f2);
    			}
    		}
    	
    	return 0;
    }
    

    其他

    世博会

    其实这题的一个重点是一个叫切比雪夫距离和曼哈顿距离互相转化的东西……
    这个知识点的板子是这道P3964 松鼠聚会
    然后主席树在这题里就扮演了一个求中位数,并求其与区间中其他元素差值和的工具
    中位数其实就可以转化成查(k)
    关于差值和:发现对于中位数(a),比他大的数(p)与他的差是(p-a),比他小的数(q)与他的差是(a-q)
    那么这一对的贡献其实就是(p-q)
    因为需要传很多参数,所以这里写非递归的话会简单很多
    这里就把所有右面的贡献加上,再把所有左边的贡献减去
    最后,因为可能中位数的个数很多,除中位数外的数是不能大小完全配对的,所以在这个过程中我们维护左右个数差,最后把相应的差掉的贡献补上。
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 100005
    
    struct vert
    {
    	ll l,r,val,sum;
    }tr[N*50];
    ll cnt=0;
    
    ll n,q;
    ll a[N],b[N];
    ll la[N],lb[N];
    ll t1,t2;
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    #define ls tr[rt].l
    #define rs tr[rt].r
    
    struct Ctr
    {
    	void clone(ll &rt)
    	{
    		tr[++cnt]=tr[rt];
    		rt=cnt;
    	}	
    	void build(ll &rt,ll l,ll r)
    	{
    		if(!rt)rt=++cnt;
    		if(l==r)return;
    		ll m=(l+r)>>1;
    		build(lson);
    		build(rson);
    	}
    	void ins(ll &rt,ll l,ll r,ll x,ll v)
    	{
    		clone(rt);
    		tr[rt].val++; tr[rt].sum+=v;
    		//cout<<rt<<' '<<tr[rt].val<<endl;
    		if(l==r)return;
    		ll m=(l+r)>>1;
    		if(m>=x)ins(lson,x,v);
    		else ins(rson,x,v);
    	}
    	ll query(ll r1,ll r2,ll l,ll r,ll k)
    	{
    		ll res=0,cn=0;
    		while(l<r)
    		{
    			ll t=tr[tr[r2].l].val-tr[tr[r1].l].val;
    			//cout<<t<<endl;
    			ll m=(l+r)>>1;
    			if(k<=t)
    			{
    				cn+=tr[tr[r2].r].val-tr[tr[r1].r].val;
    				res+=tr[tr[r2].r].sum-tr[tr[r1].r].sum;
    				r2=tr[r2].l; r1=tr[r1].l;
    				r=m;
    			}
    			else
    			{
    				k-=t; cn-=t;
    				res-=tr[tr[r2].l].sum-tr[tr[r1].l].sum;
    				r2=tr[r2].r; r1=tr[r1].r;
    				l=m+1;
    			}
    			//cout<<r2<<' '<<tr[r2].val<<endl;
    			//cout<<cn<<' '<<res<<endl;
    		}
    		return res-tr[r2].sum/tr[r2].val*cn;//这里着重理解
    	}
    }tra,trb;
    
    ll rta[N],rtb[N];
    
    int main()
    {
    	scanf("%lld%lld",&n,&q);
    	ll tt;
    	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    	for(int i=1;i<=n;i++)scanf("%lld",&tt),b[i]=a[i]-tt,a[i]+=tt;
    	for(int i=1;i<=n;i++)la[i]=a[i],lb[i]=b[i];
    	sort(la+1,la+n+1); sort(lb+1,lb+n+1);
    	for(int i=1;i<=n;i++)a[i]=lower_bound(la+1,la+n+1,a[i])-la, b[i]=lower_bound(lb+1,lb+n+1,b[i])-lb;	
    	//for(int i=1;i<=n;i++)cout<<a[i]<<' '<<b[i]<<endl;
    	//cout<<' '<<ta<<' '<<tb<<endl;
    	tra.build(rta[0],1,n);
    	trb.build(rtb[0],1,n);
    	for(int i=1;i<=n;i++)rta[i]=rta[i-1],tra.ins(rta[i],1,n,a[i],la[a[i]]);
    	for(int i=1;i<=n;i++)rtb[i]=rtb[i-1],trb.ins(rtb[i],1,n,b[i],lb[b[i]]);
    	for(int i=1;i<=q;i++)
    	{
    		scanf("%lld%lld",&t1,&t2);
    		ll k=(t2-t1+2)/2;
    		printf("%.2f
    ",(double)(tra.query(rta[t1-1],rta[t2],1,n,k)+trb.query(rtb[t1-1],rtb[t2],1,n,k))/2.0);
    	}
    	return 0;
    }
    

    dC Loves Number Theory

    (varphi(prodlimits^{r}_{i=l}a[i]))
    首先按照定义,(varphi)的求法:

    [varphi(n)=n*prodlimits_{pin prime,p|n}frac{p-1}{p} ]

    易得,(prodlimits^{r}_{i=l}a[i])的质因子集合为(a[l]~a[r])每个数质因子的并集
    我们仿照HH的项链的方法,让每一个质因子在他最后出现的那个数里被算上,即可持久化后,在新位置处理后消除旧位置的影响
    答案就是主席树上查询一个区间积,再乘上原数的区间积
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long 
    #define N 100005
    #define K 2000005
    #define mod 1000777
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    ll inv[K];
    ll mul[N];
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*80];
    ll cnt=0;
    
    void ins(ll pre,ll &rt,ll l,ll r,ll x,ll v)
    {
    	tr[rt=++cnt]=tr[pre];
    	tr[rt].val=tr[rt].val*v%mod;
    	//cout<<l<<' '<<r<<' '<<tr[rt].val<<endl;
    	if(l==r)return;
    	ll m=(l+r)>>1;
    	if(m>=x)ins(tr[pre].l,lson,x,v);
    	else ins(tr[pre].r,rson,x,v); 
    }
    
    ll query(ll rt,ll l,ll r,ll nl,ll nr)
    {
    	if(nl<=l&&r<=nr)return tr[rt].val;
    	ll m=(l+r)>>1;
    	ll res=1;
    	if(m>=nl)res=res*query(lson,nl,nr)%mod;
    	if(m<nr)res=res*query(rson,nl,nr)%mod;
    	return res; 
    }
    
    ll prime[K],tot=0;
    bool vis[K];
    
    void xxs()
    {
    	for(int i=2;i<=K-5;i++)
    	{
    		if(!vis[i])prime[++tot]=i;
    		for(int j=1;i*prime[j]<=N&&j<=tot;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)break;
    		}
    	}
    }
    
    ll n,q;
    ll a[N];
    ll t1,t2;
    ll lst[K];
    ll root[N];
    ll ans=0;
    
    int main()
    {
    	scanf("%lld%lld",&n,&q);
    	//cout<<n<<endl;
    	xxs();
    	mul[0]=1;
    	//cout<<n<<endl;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    		mul[i]=mul[i-1]*a[i]%mod;
    	}
    	inv[0]=inv[1]=1;
    	tr[0].val=1;
    	tr[0].l=tr[0].r=0;
    	//root[0]=0;
    	for(int i=2;i<=K-5;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    	for(int i=1;i<=n;i++)
    	{
    		ll t=a[i];
    		root[i]=root[i-1];
    		//cout<<t<<endl;
    		for(int j=1;prime[j]*prime[j]<=a[i];j++)
    		{
    			if(t%prime[j]==0)
    			{
    				//cout<<prime[j]<<' ';
    				ins(root[i],root[i],1,n,i,(prime[j]-1)*inv[prime[j]]%mod);
    				if(lst[prime[j]])ins(root[i],root[i],1,n,lst[prime[j]],prime[j]*inv[prime[j]-1]%mod);
    				lst[prime[j]]=i;
    				while(t%prime[j]==0)t/=prime[j];
    			}
    		}
    		if(t>1)
    		{
    			//cout<<t;
    			ins(root[i],root[i],1,n,i,(t-1)*inv[t]%mod);
    			if(lst[t])ins(root[i],root[i],1,n,lst[t],t*inv[t-1]%mod);
    			lst[t]=i;
    		}
    		//cout<<endl;
    	}
    	for(int i=1;i<=q;i++)
    	{
    		scanf("%lld%lld",&t1,&t2);
    		t1^=ans; t2^=ans;
    		//cout<<query(root[t2],1,n,t1,t2)<<' '<<mul[t2]<<' '<<inv[mul[t1-1]]<<endl;
    		ans=query(root[t2],1,n,t1,t2)*mul[t2]%mod*inv[mul[t1-1]]%mod;
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    神秘数

    求一个可重复数字集合S的最小的不能被S的子集的和表示的正整数
    对于能够表示的区间([1,tmp]),当加入新数(x)

    • 如果(x<=tmp+1),那么便可以将范围扩展到([1,tmp+x])
    • 否则,(tmp+1)即为答案

    考虑将区间内的数从小到大加进去,如果现在(mx)及以内的数都加过了,能表示([1,tmp])
    那么([mx+1,tmp+1])都可以加进去,可以区间求和,那么(mx=tmp+1)(tmp=tmp+sum)
    如果求和为0了,就是不能加了,(tmp+1)即为答案
    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define N 100005
    #define inf 1000000005
    
    #define lson tr[rt].l,l,m
    #define rson tr[rt].r,m+1,r
    
    struct vert
    {
    	ll l,r,val;
    }tr[N*50];
    ll cnt=0;
    
    void ins(ll pre,ll &rt,ll l,ll r,ll x)
    {
    	tr[rt=++cnt]=tr[pre];
    	tr[rt].val+=x;
    	if(l==r)return;
    	ll m=(l+r)>>1;
    	if(m>=x)ins(tr[pre].l,lson,x);
    	else ins(tr[pre].r,rson,x);
    }
    
    ll query(ll r1,ll r2,ll l,ll r,ll nl,ll nr)
    {
    	if(nl<=l&&r<=nr)return tr[r2].val-tr[r1].val;
    	ll m=(l+r)>>1;
    	if(m<nl)return query(tr[r1].r,tr[r2].r,m+1,r,nl,nr);
    	else if(m>=nr)return query(tr[r1].l,tr[r2].l,l,m,nl,nr);
    	else return query(tr[r1].l,tr[r2].l,l,m,nl,m)+query(tr[r1].r,tr[r2].r,m+1,r,m+1,nr);
    }
    
    ll n,t,m;
    ll t1,t2,mx,ans;
    ll root[N];
    
    int main()
    {
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++)scanf("%lld",&t),ins(root[i-1],root[i],1,inf,t);
    	scanf("%lld",&m);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%lld%lld",&t1,&t2);
    		mx=0; ans=0;
    		ll sum=0;
    		while(1)
    		{
    			sum=query(root[t1-1],root[t2],1,inf,mx+1,ans+1);
    			if(!sum)break;
    			mx=ans+1,ans+=sum; 
    		}
    		printf("%lld
    ",ans+1);
    	}
    }
    
  • 相关阅读:
    HNOI2014
    HNOI2018
    HNOI2015
    HNOI2016
    Luogu4099 HEOI2013 SAO 组合、树形DP
    CF915G Coprime Arrays 莫比乌斯反演、差分、前缀和
    CF1110H Modest Substrings AC自动机、DP
    CF1110E Magic Stones 差分
    CentOS 7 配置OpenCL环境(安装NVIDIA cuda sdk、Cmake、Eclipse CDT)
    LeetCode(134) Gas Station
  • 原文地址:https://www.cnblogs.com/zzzuozhe-gjy/p/13620756.html
Copyright © 2020-2023  润新知