CSU-ACM集训-模板-主席树

原hdu 2665 Kth number

题意

\(n\)个数和\(m\)次查询,每个查询包含区间\([x,y]\),求区间内第\(K\)大的数

思路

可持久化线段树,即主席树,第一次建立一个空的线段树,使用\(root\)下标表示访问第几次时间,数据离散化后。注意下标从1开始。
注意\(cnt\)可能是乱序的,但是\(root\)控制时间区间,即表示时间\(i\)的根节点为\(nodes[root[i]]\)\(nodes\)\(l\)\(r\)控制树的左右关系。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct{
	int l,r,sum;
}NODE;NODE nodes[20*MAXN];
int root[MAXN],a[MAXN],b[MAXN];
int n,m,cnt,p;

int getid(int x)
{//离散化 
	return lower_bound(b+1,b+1+p,x)-b;
}
void build(int l,int r,int &cur)
{//建立空树 
	nodes[cur].sum=nodes[cur].l=nodes[cur].r=0;
	cur=++cnt;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(l,mid,nodes[cur].l);
	build(mid+1,r,nodes[cur].r);
}
void update(int l,int r,int &cur,int pre,int pos)
{
	cur=++cnt;//表示新开的节点 
	nodes[cur].l=nodes[pre].l;
	nodes[cur].r=nodes[pre].r;
	nodes[cur].sum=nodes[pre].sum+1;//记录该点该时间之前有多少的数字 
	if(l==r)return ;
	int mid=(l+r)>>1;//每次按照pos的值插入到1~p的范围中 
	if(pos<=mid)update(l,mid,nodes[cur].l,nodes[pre].l,pos);
	else update(mid+1,r,nodes[cur].r,nodes[pre].r,pos);
}
int query(int l,int r,int x,int y,int key)
{
	if(l==r)return l;
	int mid=(l+r)>>1;
	int sum=nodes[nodes[y].l].sum-nodes[nodes[x].l].sum;//时间区间内个数 
	if(key<=sum)//查找右区间 
		return query(l,mid,nodes[x].l,nodes[y].l,key);
	else//查找左区间 
		return query(mid+1,r,nodes[x].r,nodes[y].r,key-sum);
}
void start()
{
	int x,y,k;

	memset(root,0,sizeof(root));
	cnt=0;
	FOR2(i,1,n)
	{
		cin>>a[i];
		b[i]=a[i];
	}
	cnt=0;
	sort(b+1,b+n+1);//离散化 
	p=unique(b+1,b+n+1)-b-1;//数据范围 
	build(1,p,root[0]); //建立时间点为0 的空树 
	FOR2(i,1,n)//i表示时间 ,按时间插入a[i]元素到线段树 
		update(1,p,root[i],root[i-1],getid(a[i])); //按照a[i]在b[i]中的大小插入到不同位置 
	while(m--)
	{
		cin>>x>>y>>k;//k=y-x-k+2;第K大和第K小的差别 
		cout<<b[query(1,p,root[x-1],root[y],k)]<<endl;//区间第K大就是求 时间区间的第K大 
	}
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
	memset(root,0,sizeof(root));
		cin>>n>>m;
		start();
	}
}

原hdu 4348 To the moon

题意

区间和线段树的可持久化,典型板子题,C表示区间加,Q表示区间查询,H表示区间查询第t时间的值,B表示返回第t时间,之后无法在前进。

基本思路

首先,需要学会基本的懒节点标记的区间和线段树,然后改,具体看代码,就是加了一层root时间节点
数组一定要开到25W左右,20W以下会爆越界错!不能开太大(超过28W,像我用结构体没空间优化),就会爆内存T_T

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct {
	int l,r;
	ll w,laz;
}NODE; NODE nodes[2500010];
int n,m,root[MAXN],cnt,x,y,d,t,now,num;
ll ans;

int build(int l,int r)
{
	int cur;
	cur=num++;
	nodes[cur].l=nodes[cur].r=nodes[cur].w=nodes[cur].laz=0;
	if(l==r)
	{
		cin>>nodes[cur].w;
		return cur;
	}
	int mid=(l+r)>>1;
	nodes[cur].l=build(l,mid);
	nodes[cur].r=build(mid+1,r);
	nodes[cur].w=nodes[nodes[cur].l].w+nodes[nodes[cur].r].w;//递归建树 
	return cur;//返回节点坐标 
}

int update(int pre,int x,int y,int l,int r,int d)
{
	int cur=num++;
	nodes[cur]=nodes[pre];
	nodes[cur].w+=d*(min(y,r)-max(x,l)+1);
	if(x<=l&&r<=y)
	{
		nodes[cur].laz+=d;//懒节点,建议使用返回值的函数 
		return cur;
	}
	int mid=(l+r)/2;
	if(x<=mid)
	{//左区间查询 
		nodes[cur].l=update(nodes[cur].l,x,y,l,mid,d);
	}
	if(y>mid)
	{//右区间查询 
		nodes[cur].r=update(nodes[cur].r,x,y,mid+1,r,d);
	}
	return cur;//返回节点坐标 
}

ll query(int cur,int x,int y,int l,int r)
{
	ll res=nodes[cur].laz*(min(y,r)-max(x,l)+1);//懒节点不用下传,这是返回函数的好处,不返回值的函数需要每次修改节点的w和子节点的laz标志 
	if(x<=l&&r<=y)
	{
		return nodes[cur].w;
	}
	int mid=(l+r)/2;
	if(x<=mid)res+=query(nodes[cur].l,x,y,l,mid);
	if(y>mid)res+=query(nodes[cur].r,x,y,mid+1,r);
	return res;
}

int main()
{
	bool flag=false;
	while(cin>>n>>m)
	{
		if(flag)cout<<endl;
		else flag=true;
		memset(root,0,sizeof(root));
		memset(nodes,0,sizeof(nodes));
		num=now=0;
		root[now]=build(1,n);
		while(m--)
		{
			char op;cin>>op;
			while(op=='\n')cin>>op;
			if(op=='C')
			{
				cin>>x>>y>>d;
				now++;//线段树时间坐标 
				root[now]=update(root[now-1],x,y,1,n,d);
			}
			if(op=='Q')
			{
				cin>>x>>y;
				cout<<query(root[now],x,y,1,n)<<endl;
			}
			if(op=='H')
			{ 
				cin>>x>>y>>t;
				cout<<query(root[t],x,y,1,n)<<endl;
			}
			if(op=='B')
			{
				cin>>now;
			}
		}
	}
	return 0;
}

原hdu 6278 Just h-index

题意

与hdu 2665 类似,查找区间内第\(K\)小的数\(a_k\),使得满足\(a_k-1<=\){大于\(a_k-1\)的个数}

基本思路

二分查找第K小,即mid,离散化查询第K-1个数,使得该数满足条件 。关键在于二分

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct{
	int l,r;
	ll w;
}NODE;NODE nodes[24*MAXN];
int root[24*MAXN],num,now,n,q,p;
ll arr[MAXN],b[MAXN];
int getid(ll x)
{
	return lower_bound(b,b+p,x)-b+1;
}
void build(int l,int r,int &cur)
{//建立空树 
	nodes[cur].l=nodes[cur].r=nodes[cur].w=0;
	num++;
	cur=num;
	if(l==r)return  ;
	int mid=(l+r)>>1;
	build(l,mid,nodes[cur].l);
	build(mid+1,r,nodes[cur].r);
}
void update(int l,int r,int &cur,int pre,int pos)
{
	num++;//建立新节点
	cur=num;
	nodes[cur].l=nodes[pre].l;
	nodes[cur].r=nodes[pre].r;
	nodes[cur].w=nodes[pre].w+1;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(pos<=mid)update(l,mid,nodes[cur].l,nodes[pre].l,pos);
	else update(mid+1,r,nodes[cur].r,nodes[pre].r,pos);
}

int query(int l,int r,int x,int y,int key)
{
	if(l==r)return l;
	int mid=(l+r)>>1;
	int c=nodes[nodes[y].l].w-nodes[nodes[x].l].w;//区间个数 
	if(key<=c)return query(l,mid,nodes[x].l,nodes[y].l,key);//左区间
	else return query(mid+1,r,nodes[x].r,nodes[y].r,key-c);//右区间 
}
void start()
{
	p=num=now=0;
	
	FOR(i,0,n)
	{
		cin>>arr[i];
		b[i]=arr[i];
	}
	
	sort(b,b+n);
	p=unique(b,b+n)-b;//离散化 
	
	build(1,p,root[0]);
	FOR(i,0,n)update(1,p,root[i+1],root[i],getid(arr[i]));
	
	while(q--)
	{
		int x,y;cin>>x>>y;
		int l=1,r=y-x+1,rr=r,ans=1;//l r表示查询的区间大小,确定mid 
		while(l<=r)
		{
			int mid=(l+r)>>1;
			int t=query(1,p,root[x-1],root[y],mid);//求区间第mid大的数 
//			cout<<"mid="<<mid<<"t="<<t<<"b="<<b[t-1]<<endl;
			if(b[t-1]>=rr-mid+1) {
				ans=rr-mid+1;
				r=mid-1; 
			}
			else l=mid+1;
		}
		cout<<ans<<endl;
	}
}
int main()
{
	while(cin>>n>>q)
	{
		start();
	}
	return 0;
}
posted @ 2019-07-25 20:46  一块钱的争论  阅读(266)  评论(0编辑  收藏  举报