题解 SP3946 【MKTHNUM - K-th Number】
第一步 读题
题目我就不放了(其实是翻译人的 )。
给一个简化版的题目:给定一个长度为
第二步 思路
要我们求
首先是暴力枚举,对于每一次询问直接枚举
然后我们要考虑优化。我们发现这道题让我们求区间内第
我这个蒟蒻只会可持久化线段树,那就讲这个吧。初学者可以来看这篇博文,蒟蒻会详细讲解可持久化线段树。
第一部分 基础介绍&存储插入
由于可持久化线段树的创造者的名字缩写为 HJT,与我们敬爱的前国家主席的名字相同,故得主席树知名。算法主要思想就是保存多个历史版本的线段树。但是怎么保存呢?每一次修改都保存?空间不就炸了吗......
但是我们仔细想想,发现每次更改的仅仅是叶子节点到根节点这一条链上的结点。也就是只更改了
但是我们要特别注意主席树不能使用堆式存储法,就是说不能用
这样我们可以在每次插入的时候进行一次更改存储。
第二部分 查找原理
我们已经存好了每一个版本。现在要求出
那我们现在把范围扩展到
想一下,我们发现可以应用 前缀和。我们用
第三部分 空间
这一部分真的要单出来列。。因为你开空间不能吝啬,不然会爆炸。。我们先分析一下:
首先,我们动态开点,线段树会出现
第三部分 代码
终于到了代码部分了。。。(警告:由于快读较长,建议跳过!)
#include<bits/stdc++.h>//万能头
using namespace std;
using std::cin;
using std::cout;
using std::endl;
namespace IN{//读入优化,建议跳过
const int MAX_INPUT = 1000000;
#define getc() (p1==p2&&(p2=(p1=buf)+inbuf->sgetn(buf,MAX_INPUT),p1==p2)?EOF:*p1++)
char buf[MAX_INPUT],*p1,*p2;
template<typename T>inline bool read(T &x) {
static std::streambuf *inbuf=cin.rdbuf();
x=0;
register int f=0,flag=false;
register char ch=getc();
while(!isdigit(ch)){
if (ch=='-') f=1;
ch=getc();
}
if(isdigit(ch)) x=x*10+ch-'0',ch=getc(),flag=true;
while(isdigit(ch)) {
x=x*10+ch-48;
ch=getc();
}
x=f?-x:x;
return flag;
}
template<typename T,typename ...Args>inline bool read(T& a,Args& ...args) {
return read(a)&&read(args...);
}
#undef getc
}
namespace OUT{//输出优化,建议跳过
template<typename T>inline void put(T x){
static std::streambuf *outbuf=cout.rdbuf();
static char stack[21];
static int top=0;
if(x<0){
outbuf->sputc('-');
x=-x;
}
if(!x){
outbuf->sputc('0');
outbuf->sputc('\n');
return;
}
while(x){
stack[++top]=x%10+'0';
x/=10;
}
while(top){
outbuf->sputc(stack[top]);
--top;
}
outbuf->sputc('\n');
}
inline void putc(const char ch){
static std::streambuf *outbuf=cout.rdbuf();
outbuf->sputc(ch);
}
inline void putstr(string s){
for(register int i=0;i<s.length();i++) putc(s[i]);
}
template<typename T>inline void put(const char ch,T x){
static std::streambuf *outbuf=cout.rdbuf();
static char stack[21];
static int top = 0;
if(x<0){
outbuf->sputc('-');
x=-x;
}
if(!x){
outbuf->sputc('0');
outbuf->sputc(ch);
return;
}
while(x){
stack[++top]=x%10+'0';
x/=10;
}
while(top){
outbuf->sputc(stack[top]);
--top;
}
outbuf->sputc(ch);
}
template<typename T,typename ...Args> inline void put(T a,Args ...args){
put(a);put(args...);
}
template<typename T,typename ...Args> inline void put(const char ch,T a,Args ...args){
put(ch,a);put(ch,args...);
}
}
using IN::read;
using OUT::put;
using OUT::putc;
using OUT::putstr;
//定义一些
#define N (int)2e5+5
#define int long long
#define mid ((l+r)>>1)
int cnt,n,m,p,a[N],b[N],q[N];//cnt表示结点数,n,m,a读入,还有离散序列
struct CTnode{//结构体
int s,l,r;//s表示数,
}t[N<<5];//结构体数组
namespace Chairman_Tree{//如果写好多东西放在一起有可能分不清
inline int build(int l,int r){//这里建树
int rt=++cnt;//保存结点编号
t[rt].s=0;//初始化
if(l<r) t[rt].l=build(l,mid),t[rt].r=build(mid+1,r);//可以建树就继续建树
return rt; //返回结点编号
}
inline int update(int k,int l,int r,int rt){//k表示旧结点位置
int u=++cnt;//保存一下
t[u].l=t[k].l,t[u].r=t[k].r,t[u].s=t[k].s+1;//复制,更改。点数为上一课加1
if(l<r)//可以更新
if(rt<=mid) t[u].l=update(t[k].l,l,mid,rt);//左儿子更新
else t[u].r=update(t[k].r,mid+1,r,rt);//右儿子更新
return u;
}
inline int query(int x,int y,int l,int r,int k){//查询第k小数
if(l>=r) return l;
int u=t[t[y].l].s-t[t[x].l].s;//区间减法得到左儿子信息
if(u>=k) return query(t[x].l,t[y].l,l,mid,k);//在左儿子中
else return query(t[x].r,t[y].r,mid+1,r,k-u);//在右儿子中
}
inline void init(){
read(n,m);//读入
for(int i=1;i<=n;i++) read(a[i]),b[i]=a[i];//读入并赋值
sort(b+1,b+n+1);//这里先搜索
p=unique(b+1,b+n+1)-b-1;//不重复数字的个数
q[0]=build(1,p);//简历一颗空树,并赋值
for(int i=1;i<=n;i++){
int k=lower_bound(b+1,b+p+1,a[i])-b;//查找第一个大于等于a[i]的数
q[i]=update(q[i-1],1,p,k);//更新并赋值
}
}
inline void solve(){//主函数(话说都变味了)
init();//读入初始化
for(int i=1,x,y,z;i<=m;i++)//m次查询
read(x,y,z),put('\n',b[query(q[x-1],q[y],1,p,z)]);//改成b的输出。由于有影响,所以是q[x-1]
}
}
signed main(signed argc, char const *argv[]){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
Chairman_Tree::solve();//运行solve
return 0;
}
第四步 其他
- 撰文不易,大佬勿喷!
- 文章若有
bug请私信作者,感激不尽! - 如果有帮助,请帮忙点下赞,感谢!