学习笔记:OI常用模板(一)

版权声明:转载请附带原文链接,请勿随意删除原文内容,允许少量格式和/或内容修改,谢谢! 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。
其实朴素的倍增,每次以 2 i 2^i 级向上跳跃还是不够快,对数优化可以加速这一过程。

鉴于有时候我们假设根节点的 2 0 2^0 级父亲为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;
			}
			...
		}
	}
}

若有谬误,敬请斧正。

猜你喜欢

转载自blog.csdn.net/weixin_37661548/article/details/87620989
今日推荐