原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=='
')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;
}