版权声明:转载请附带原文链接,请勿随意删除原文内容,允许少量格式和/或内容修改,谢谢! https://blog.csdn.net/weixin_37661548/article/details/87620989
快速读入
通用
template<typename T> void qread(T &sum)
{
sum = 0;
register int sym = 1;
register char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-') sym = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
sum = (sum << 1) + (sum << 3) + ch - '0';
ch = getchar();
}
sum *= sym;
}
注意快读是可以被卡的。(比如:故意在数据中插入空格)
快速输出
通用
template<typename T> void qwrite(const T x)
{
if (x < 0)
{
putchar('-');
qwrite(-x);
}
else
{
if (x >= 10) qwrite(x / 10);
putchar(x % 10 + '0');
}
}
不能输出换行符。
优先队列优化 Dijkstra
洛谷 P4779 【模板】单源最短路径(标准版)
struct node
{
int pos,dist;
node(void) : pos(0),dist(0) {}
node(int _pos,int _dist) : pos(_pos),dist(_dist) {}
friend bool operator < (const node &opa,const node &opb)
{
return opa.dist > opb.dist; //注意方向。
}
};
priority_queue<node> Q;
void dijkstra()
{
dis[s] = 0;
Q.push(node(s,0));
while (!Q.empty())
{
node cur = Q.top();
Q.pop();
if (cur.dist != dis[cur.pos]) continue;
for (int i = head[cur.pos];i;i = info[i].nxt)
{
int v = info[i].to;
if (dis[v] > dis[cur.pos] + info[i].wgt)
{
dis[v] = dis[cur.pos] + info[i].wgt;
Q.push(node(v,dis[v]));
}
}
}
}
void work()
{
dijkstra();
for (int i = 1;i <= n;++i)
{
printf("%d ",dis[i]);
}
}
求单源最短路径,不可以处理负权边、负环。
注意:若一题中边权值大于0,一般在卡SPFA。
SLF优化 SPFA
洛谷 P4779 【模板】单源最短路径(标准版)
int n,m,s,e,ui,vi,wi;
int head[MAXN],dis[MAXN],vis[MAXN];
deque<int> Q;
void spfa()
{
Q.push_back(s);
vis[s] = true;
dis[s] = 0;
while (!Q.empty())
{
int u = Q.front();
Q.pop_front();
vis[u] = false;
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (dis[v] > dis[u] + info[i].wgt)
{
dis[v] = dis[u] + info[i].wgt;
if (!vis[v])
{
if (dis[v] > Q.front()) Q.push_back(v);
else Q.push_front(v);
vis[v] = true;
}
}
}
}
}
void work()
{
spfa();
for (int i = 1;i <= n;++i)
{
qwrite(dis[i]);
putchar(' ');
}
}
求单源最短路径,可以处理负权边、判断负环。
仅当存在负权边时才考虑使用SPFA。
优先队列优化 Prim
LOJ #123. 最小生成树
struct node
{
int pos,dist;
node(void) : pos(0),dist(0) {}
node(int _pos,int _dist) : pos(_pos),dist(_dist) {}
friend bool operator < (const node &opa,const node &opb)
{
return opa.dist > opb.dist;
}
};
priority_queue<node> Q;
void prim() //每次在MST之外的边中取最小边进行扩展。
{
Q.push(node(1,0));
while (!Q.empty())
{
node cur = Q.top();
Q.pop();
if (vis[cur.pos]) continue;
vis[cur.pos] = true;
ans += cur.dist;
for (int i = head[cur.pos];i;i = info[i].nxt)
if (!vis[info[i].to]) Q.push(node(info[i].to,info[i].wgt)); //无向图,用vis[]标记避免重复访问。
}
}
void work()
{
prim();
printf("%lld",ans);
}
和Dijkstra较为相似,注意区分。
适用于稠密图。
Kruskal
LOJ #123. 最小生成树
struct node
{
int pos,dist;
node(void) : pos(0),dist(0) {}
node(int _pos,int _dist) : pos(_pos),dist(_dist) {}
friend bool operator < (const node &opa,const node &opb)
{
return opa.dist > opb.dist;
}
};
bool cmp(edge opa,edge opb)
{
return opa.wgt < opb.wgt;
}
...
int fa[MAXN];
int find(int opa)
{
return fa[opa] == opa ? opa : fa[opa] = find(fa[opa]);
}
void merge(int opa,int opb)
{
fa[find(opa)] = find(opb);
}
void kruskal()
{
int cnt = 0;
for (int i = 1;i <= m;++i)
{
if (cnt == n - 1) break;
if (find(info[i].from) == find(info[i].to)) continue;
merge(info[i].from,info[i].to);
ans += info[i].wgt;
cnt++;
}
}
void init()
{
...
for (int i = 1;i <= n;++i) fa[i] = i;
...
sort(info + 1,info + m + 1,cmp);
}
void work()
{
kruskal();
printf("%lld",ans);
}
适用于稀疏图。
数据范围小时,可以求最小瓶颈路,即利用Kruskal将所有边从小到大排序的特性。
注意 :Kruskal的建边方式和链式前向星不一样。
对数优化 树上倍增 LCA
洛谷 P3379 【模板】最近公共祖先(LCA)
...
int fa[MAXN][20],lg[MAXN];
void dfs(int u,int f,int d)
{
fa[u][0] = f;
dep[u] = d;
for (int i = 1;i <= lg[d];++i) fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (v == f) continue;
dfs(v,u,d + 1);
}
}
int LCA(int u,int v)
{
if (dep[u] < dep[v]) swap(u,v);
while (dep[u] != dep[v]) u = fa[u][lg[dep[u] - dep[v]]];
if (u == v) return u;
for (int i = lg[dep[u]];i >= 0;--i)
{
if (fa[u][i] != fa[v][i])
{
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void init()
{
...
lg[0] = -1;
for (int i = 1;i <= n;++i) lg[i] = lg[i >> 1] + 1;
dfs(s,0,0);
}
void work()
{
for (int i = 1;i <= m;++i)
{
qread(ui);
qread(vi);
qwrite(LCA(ui,vi));
putchar('\n');
}
}
这个模板对洛谷上的对数优化进行了改进,不必每次使用时-1。
其实朴素的倍增,每次以
级向上跳跃还是不够快,对数优化可以加速这一过程。
鉴于有时候我们假设根节点的
级父亲为0,那么fa[][]
数组就要初始化为-1。
树链剖分 LCA
洛谷 P3379 【模板】最近公共祖先(LCA)
int heavySon[MAXN],top[MAXN],dep[MAXN],size[MAXN],fa[MAXN];
void preDfs(int u,int f,int d)
{
int maxs = -1;
dep[u] = d;
fa[u] = f;
size[u] = 1;
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (v == f) continue;
preDfs(v,u,d + 1);
size[u] += size[v];
if (size[v] > maxs)
{
heavySon[u] = v;
maxs = size[v];
}
}
}
void postDfs(int u,int t)
{
top[u] = t;
if (!heavySon[u]) return;
postDfs(heavySon[u],t);
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (v == fa[u] || v == heavySon[u]) continue;
postDfs(v,v);
}
}
int LCA(int u,int v)
{
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u,v);
u = fa[top[u]];
}
if (u == v) return u;
return dep[u] > dep[v] ? v : u;
}
似乎比倍增快一点儿?
树链剖分 朴素树链剖分
洛谷 P3384 【模板】树链剖分
#include <bits/stdc++.h>
#define lson l,m,n << 1
#define rson m + 1,r,n << 1 | 1
using namespace std;
const int MAXN = 100005;
struct edge
{
int to,nxt;
} info[MAXN << 1];
int n,m,r,mod;
//题目中的变量。
int op,x,y,z,e,nid,res;
//操作代号;变量;边累加器;哈希后节点索引;局部累加器。
int oriw[MAXN],head[MAXN],sum[MAXN << 2],lazy[MAXN << 2];
//树上节点权值;链式前向星;区间和;懒标记。
int fa[MAXN],dep[MAXN],size[MAXN],son[MAXN],top[MAXN],id[MAXN],wgt[MAXN];
//节某点的父节点;某节点的深度;以某个节点为树根的子树的节点个数;某个节点的重儿子;某条重链的链端节点;
//哈希后原树上节点的编号;哈希后原树上节点的权值。
namespace Tree_Chain_Partition //树链剖分.
{
void pre_dfs(int u,int f,int d) //当前节点编号;当前节点父节点;当前节点深度。
//第一次DFS求出每个节点的重儿子。
{
int maxs = -1; //存放当前节点的子树的节点个数。
dep[u] = d; //当前节点深度。
fa[u] = f; //当前节点深度的父节点。
size[u] = 1; //以当前节点为根的子树的节点个数(如果没有子节点,那么就只有根一个节点)。
for (int i = head[u]; i != -1; i = info[i].nxt) //枚举当前节点所有子节点。
{
int v = info[i].to;
if (v == f) continue; //判断返祖边。
pre_dfs(v,u,d + 1);
size[u] += size[v]; //累加子树的节点个数。
if (size[v] > maxs) //贪心,求出所有子树中节点数最多的那个。
{
maxs = size[v];
son[u] = v; //作根时节点数最多的子节点是当前节点的“重儿子”。
}
}
}
void post_dfs(int u,int t) //当前节点;当前节点所在的链的链端节点。
//第二次DFS用来求出重链。
{
id[u] = ++nid; //给节点编号。
wgt[nid] = oriw[u]; //将树哈希成线性的(便于线段树的处理),使用节点编号可以保证一一对应。
top[u] = t; //记录链端节点。
if (!son[u]) return; //如果存在重儿子(只有当前节点的重儿子才和当前节点在同一重链上)。
post_dfs(son[u],t); //重儿子和当前节点在同一条重链上,它们的链端节点是一样的。这样保证了一条链
//上的节点编号是连续的,便于后续线段树处理。
for (int i = head[u]; i != -1; i = info[i].nxt) //枚举当前节点的所有子节点。
{
int v = info[i].to;
if (v == fa[u] || v == son[u]) continue; //判断返祖边;重儿子不必再算一次。
post_dfs(v,v); //若不是重儿子,那么这个子节点就单独成链,它就是链顶。
}
}
}
using namespace Tree_Chain_Partition;
namespace Segment_Tree //线段树.
{
//L和R指的是查询/修改区间,而l和r指的是当前搜索到的区间.
void push_up(int n)
{
sum[n] = (sum[n << 1] + sum[n << 1 | 1]) % mod;
}
void push_down(int n,int r)
{
if (lazy[n])
{
lazy[n << 1] += lazy[n];
sum[n << 1] += lazy[n] * (r - (r >> 1));
sum[n << 1] %= mod;
lazy[n << 1 | 1] += lazy[n];
sum[n << 1 | 1] += lazy[n] * (r >> 1);
sum[n << 1 | 1] %= mod;
lazy[n] = 0;
//此时节点n已经把标记下发给他的子节点(子区间),清空节点n的标记,避免重复累加.
}
}
void update(int L,int R,int D,int l,int r,int n) //n就是当前节点(区间)编号,D是要增加的值.
{
if (L <= l && r <= R)
{
lazy[n] += D; //更新当前节点的lazy标记.
sum[n] += D * (r - l + 1);
//当前节点代表区间为[l,r],共有r - l + 1个子节点.每个子节点要加上
//D,相当于当前节点加上D * (r - l + 1).
sum[n] %= mod;
return;
}
push_down(n,r - l + 1);
//由于子节点并未实际更新,需要向下更新标记和sum.
int m = (l + r) >> 1;
if (L <= m) update(L,R,D,lson);
if (m < R) update(L,R,D,rson);
push_up(n);
}
void query(int L,int R,int l,int r,int n)
{
if (L <= l && r <= R) //查询\修改区间完全覆盖当前操作区间.
{
res += sum[n]; //可以直接返回当前节点的值,不必再访问其子节点(子区间).
res %= mod;
return;
}
push_down(n,r - l + 1); //如果没有完全覆盖,那么就要访问他的子节点了.由于子节点的值并未实际更新,
//要传递标记更新他们的值.
int m = (l + r) >> 1;
if (L <= m) query(L,R,lson);
if (m < R) query(L,R,rson); //Q3.
}
void build(int l,int r,int n)
//这个也很明显嘛......
{
if (l == r)
{
sum[n] = wgt[l];
sum[n] %= mod;
return;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
push_up(n);
}
}
using namespace Segment_Tree;
namespace Operations //区间/单点查询和区间/单点修改.
{
void path_add(int x,int y,int z)
{
z %= mod; //为什么要取模?
while (top[x] != top[y]) //当两节点不在同一重链上。(如果在同一重链上,可以直接区间修改)
//空降坐标:卿学姐的视频12:45。
{
if (dep[top[x]] < dep[top[y]]) swap(x,y); //链端节点的编号总要比这条链上的其他节点小。
update(id[top[x]],id[x],z,1,n,1); //直接求解这一条链的和,不用一个一个修改。
x = fa[top[x]]; //跳出当前这一条链。注意!不要忘记取fa[]值!
}
if (dep[x] > dep[y]) swap(x,y); //主要是因为线段树查询左边界要小于右边界......
update(id[x],id[y],z,1,n,1); //已经在一条重链上,区间修改。
}
int path_query(int x,int y) //和上面的path_add()是一样的。
{
int ans = 0; //全局累加器。
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x,y);
res = 0; //局部累加器。
query(id[top[x]],id[x],1,n,1);
ans += res;
ans %= mod;
x = fa[top[x]]; //注意!不要忘记取fa[]值!
}
if (dep[x] > dep[y]) swap(x,y);
res = 0;
query(id[x],id[y],1,n,1);
ans += res;
return ans %= mod;
}
void son_add(int x,int z) //size[]的作用就是找到一颗哈希后的子树的左右界。
{
update(id[x],id[x] + size[x] - 1,z,1,n,1);
}
int son_query(int x)
{
res = 0;
query(id[x],id[x] + size[x] - 1,1,n,1); //注意!不要写成size[id[x]]!
return res;
}
}
using namespace Operations;
void init()
{
freopen("in.txt","r",stdin);
scanf("%d%d%d%d",&n,&m,&r,&mod);
memset(head,-1,sizeof(head));
for (int i = 1; i <= n; i++) oriw[i] = qread();
for (int i = 1; i <= n - 1; i++)
{
x = qread();
y = qread();
addedge(x,y);
addedge(y,x);
}
pre_dfs(r,0,1);
post_dfs(r,r);
build(1,n,1);
}
二分答案 整数二分 二分找下界 / 最大值最小
洛谷 P2680 运输计划
while (L <= R)
{
mid = (L + R) >> 1;
if (judge(mid)) L = mid + 1;
else R = mid - 1;
}
printf("%d",L);
二分答案 整数二分 二分找上界 / 最小值最大
while (L <= R)
{
mid = (L + R) >> 1;
if (judge(mid)) L = mid + 1;
else R = mid - 1;
}
printf("%d",R);
树上差分 点差分
cnt[u]++;
cnt[v]++;
cnt[lca(u,v)]--;
cnt[fa[lca(u,v)]]--;
树上差分 边差分
洛谷 P2680 运输计划
void prepare(int u,int f)
{
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (v == f) continue;
oriWgt[v] = info[i].wgt;
//维护出**每个节点到其父节点的边的权值**,编号为该节点的编号,
//以便和下面的cnt[]数组配合。
...
prepare(v,u);
}
}
cnt[u]++;
cnt[v]++;
cnt[lca(u,v)] -= 2;
int dfs(int u,int f)
{
for (int i = head[u];i;i = info[i].nxt)
{
int v = info[i].to;
if (v == f) continue;
cnt[u] += dfs(v,u);
}
return cnt[u];
}
Splay 普通平衡树
洛谷 P3369 【模板】普通平衡树
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n,op;
namespace Splay
{
/*
------函数------------------方法-------------------操作对象
inline pushUp() -------------------------------------节点
inline bool get() //两个短的直接记!-----------------节点
inline rotate() --//最基本操作 ----------------------节点
splay() ----------//不停rotate()变成splay()----------节点
find() -----------//splay()和find()功能类似 ---------节点
int Next() -------//find next好不好记qwq ------------数字
insert() --------------------------------------------数字
remove() ---------//插入删除是一对 ------------------数字
int queryKth() --------------------------------------数字
inline int queryRank() //两个查询是一对qwq ----------数字
*/
int son[MAXN][2],fa[MAXN],cnt[MAXN],size[MAXN],val[MAXN];
//爸爸+儿子+两计数(节点个数+重复个数)+记录值
int Root,nid;
inline void pushUp(int x)
{
size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x];
//还要加上自己本身重复的次数。
}
inline bool get(int x)
{
return son[fa[x]][1] == x;
//1代表右儿子。如果x是右儿子,恰好返回1.
}
inline void rotate(int x)
{
int y = fa[x],z = fa[fa[x]],which = get(x),w = son[x][which ^ 1];
son[y][which] = w;
//X的 X原来在Y的 相对的 那个儿子 变成了 Y原来是X的那个儿子
fa[w] = y;
son[z][get(y)] = x;
//x变到原来y的位置。
fa[x] = z;
son[x][which ^ 1] = y;
//Y变成了 X原来在Y的 相对的那个儿子
fa[y] = x;
//注意!!!第三句只能放最底下,第二句必须放第三句上面,第一句可以随便放!!!
//因为第三句修改了y是fa[y]的左右儿子。
//记忆:yx zx xy
pushUp(y);
pushUp(x); //注意!!!顺序不能换,因为此时y是x的儿子。要从底向上pushUp.
}
void splay(int x,int aim = 0)
{
while (fa[x] != aim) //一直旋转到x成为goal的儿子(注意是儿子!)
{
int y = fa[x],z = fa[fa[x]];
//注意!!!y和z必须放在循环内,因为每次rotate()后他们都会更新。
if (z != aim)
{
if (get(x) == get(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if (aim == 0) Root = x; //不过如果aim等于0或不带参,就转到根节点。
}
void find(int x) //如果不存在这个数呢,返回这个数的前驱或者后继节点。
{
int cur = Root;
while (val[cur] != x && son[cur][x > val[cur]])
{
cur = son[cur][x > val[cur]];
}
splay(cur);
/*
从根节点开始,左侧都比他小,右侧都比他大,
所以只需要相应的往左/右递归.
如果当前位置的val已经是要查找的数,
那么直接把他Splay到根节点,方便接下来的操作.
*/
}
int Next(int x,int f) //查找x的前驱(0)或者后继(1).
{
find(x);
if ((val[Root] > x && f) || (val[Root] < x && !f)) return Root;
//假如x存在,则上面的if语句并不会执行。
//假如x不存在,那么它的前驱或后继就是Root。如果我们的查询恰好与x和Root的关系吻合,直接返回Root
//即可。如果相反,按照下面程序也可以找到前驱后继。
int cur = son[Root][f]; //查找后继的话在右儿子上找,前驱在左儿子上找。
while (son[cur][f ^ 1]) cur = son[cur][f ^ 1]; //要反着跳转,否则会越来越大(越来越小)
return cur;
}
void insert(int x)
{
int cur = Root,f = 0; //记录cur的父节点。
while (val[cur] != x && cur)
{
f = cur;
cur = son[cur][x > val[cur]];
}
if (cur) cnt[cur]++;
else
{
cur = ++nid;
if (f) son[f][x > val[f]] = cur;
//当f为0时,cur就是根节点。0是虚拟的,令0为根节点的爸爸。
//仅当cur不是根节点时,才有必要建立他与他爸爸之间的关系。
val[cur] = x;
fa[cur] = f;
son[cur][0] = son[cur][1] = 0;
size[cur] = cnt[cur] = 1;
}
splay(cur); //把当前位置移到根,保证结构的平衡.
/*
往Splay中插入一个数,
类似于Find操作,只是如果是已经存在的数,就可以直接在查找
到的节点的进行计数.如果不存在,在递归的查找过程中,
会找到他的父节点的位置,然后就会发现底下没有啦。
所以这个时候新建一个节点就可以了.
*/
}
void remove(int x)
{
int last = Next(x,0),nxt = Next(x,1);
splay(last);
//find()也是提到根,但find()提的是某个数代表的节点,而splay()提的直接是某个节点。
splay(nxt,last);
int del = son[nxt][0];
if (cnt[del] > 1)
{
cnt[del]--;
splay(del);
}
else son[nxt][0] = 0;
/*
现在就很简单啦,首先找到这个数的前驱,把他Splay到根节点
然后找到这个数后继,把他旋转到前驱的底下,比前驱大的数是后继,
在右子树,比后继小的且比前驱大的有且仅有当前数,在后继的左子树上面,
因此直接把当前根节点的右儿子的左儿子删掉就可以啦qwq
*/
}
int queryKth(int k) //查询第k小的数是多少。(注意取val[]!)
{
int cur = Root;
while (true)
{
if (son[cur][0] && k <= size[son[cur][0]]) cur = son[cur][0];
else if (size[son[cur][0]] + cnt[cur] >= k) return cur;
else
{
k -= size[son[cur][0]] + cnt[cur];
cur = son[cur][1];
}
//这个和主席树差不多了嘛......
}
}
inline int queryRank(int x)
{
find(x);
return size[son[Root][0]]; //如果我们找到了权值为x的节点,那么答案就是他的左子树的大小.
//和下面求kth时不同,由于边框是不算数的(假设有数-inf,1,问第一小的数)
//,1的左子树节点个数就是1,不用加1。
}
}
//若splay()只带一个参,那么是将某节点转到根;
//若带两个参,是将某节点转到目标节点的儿子;
//find()是将某个数代表的节点转到根。
using namespace Splay;
void init()
{
freopen("in.txt","r",stdin);
scanf("%d",&n);
insert(0x3f3f3f3f);
insert(-0x3f3f3f3f); //加边框。
}
void work()
{
int x;
for (int i = 1; i <= n; ++i)
{
qread(op), qread(x);
switch (op)
{
case 1 :
{
insert(x);
break;
}
case 2 :
{
remove(x);
break;
}
case 3 :
{
printf("%d\n",queryRank(x));
break;
}
case 4 :
{
printf("%d\n",val[queryKth(x + 1)]); //因为加了一个边框,所以排名要+1。
break;
}
case 5 :
{
printf("%d\n",val[Next(x,0)]);
break;
}
case 6 :
{
printf("%d\n",val[Next(x,1)]);
break;
}
}
}
}
鸣谢:小蒟蒻yyb《Splay入门解析【保证让你看不懂(滑稽)】》
https://blog.csdn.net/qq_30974369/article/details/77587168
无注释版
...
namespace Splay
{
int Root, nid;
int son[MAXN][2], fa[MAXN], cnt[MAXN], size[MAXN], val[MAXN];
inline void pushUp(int x)
{
size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x];
}
inline bool get(int x)
{
return son[fa[x]][1] == x;
}
inline void rotate(int x)
{
int y = fa[x], z = fa[y], k = get(x);
son[y][k] = son[x][k ^ 1];
fa[son[x][k ^ 1]] = y;
son[z][get(y)] = x;
fa[x] = z;
son[x][k ^ 1] = y;
fa[y] = x;
pushUp(y);
pushUp(x);
}
void splay(int x,int aim)
{
while (fa[x] != aim)
{
int y = fa[x], z = fa[y];
if (z != aim)
{
if (get(x) == get(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if (aim == 0) Root = x;
}
void find(int x)
{
int cur = Root;
while (val[cur] != x && son[cur][x > val[cur]])
cur = son[cur][x > val[cur]];
splay(cur,0);
}
int Next(int x,int f)
{
find(x);
if ((val[Root] > x && f) || (val[Root] < x && !f)) return Root;
int cur = son[Root][f];
while (son[cur][f ^ 1]) cur = son[cur][f ^ 1];
return cur;
}
void insert(int x)
{
int cur = Root, f = 0;
while (val[cur] != x && cur)
{
f = cur;
cur = son[cur][x > val[cur]];
}
if (cur) cnt[cur]++;
else
{
cur = ++nid;
if (f) son[f][x > val[f]] = cur;
fa[cur] = f;
val[cur] = x;
size[cur] = cnt[cur] = 1;
son[cur][0] = son[cur][1] = 0;
}
splay(cur,0);
}
void remove(int x)
{
int pre = Next(x,0), suc = Next(x,1);
splay(pre,0);
splay(suc,pre);
int del = son[suc][0];
if (cnt[del] > 1)
{
cnt[del]--;
splay(del,0);
}
else son[suc][0] = 0;
}
int queryKth(int k)
{
int cur = Root;
while (true)
{
if (son[cur][0] && size[son[cur][0]] >= k) cur = son[cur][0];
else if (size[son[cur][0]] + cnt[cur] >= k) return cur;
else
{
k -= size[son[cur][0]] + cnt[cur];
cur = son[cur][1];
}
}
}
inline int queryRank(int x)
{
find(x);
return size[son[Root][0]];
}
}
using namespace Splay;
void init()
{
...
insert(0x3f3f3f3f);
insert(-0x3f3f3f3f);
}
void work()
{
for (int i = 1; i <= n; ++i)
{
qread(op), qread(num);
switch (op)
{
...
case 4 :
{
qwrite(val[queryKth(num + 1)]);
putchar('\n');
break;
}
...
}
}
}