最大流(max flow)

本文深入探讨了快速幂算法的原理及其在不同场景下的应用,包括递归与非递归实现,并讨论了其在高精度计算中的推广。同时,详细介绍了无旋Treap的结构与操作,如合并、分裂等,以及其实现代码,适用于解决复杂的数据结构问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快速幂

问题

输入x, n, p,计算x ^ n \bmod p

基本算法

一个暴力的做法就是直接把nx乘起来,时间复杂度为O(n)

考虑一个递归的计算方法:

x^{n}={ \begin{cases} 1,&{\mbox{如果}}n=0\\ x\,(x^2)^{\frac {n-1}{2}},&{\mbox{如果}}n{\mbox{是奇数}}\\ (x^2)^{\frac {n}{2}},&{\mbox{如果}}n{\mbox{是偶数}}. \end{cases}}

每次递归n都会变为原先的一半,所以时间复杂度为O(\log n)

对于非递归的做法,考虑n的二进制表示b_k b_{k-1} \cdots, b_0

x^n = b_k x^{2^k} \times b_{k-1} x^{2^{k-1}} \times \cdots \times b_0 x^{2^0}

对于x^{2^0}, x^{2^1}, \ldots, x^{2^k}每一项是前面一项的平方, 可以在O(k)的时间内计算,然后再根据b_i的值, 决定每个x^{2^i}是否乘入答案中。

代码

递归的实现

long long pow(long long x, long long n, long long p) {
    if (n == 0) {
        return 1;
    } else {
        int t = pow(x * x % p, n / 2, p);
        if (n % 2 == 1) {
            t = t * x % p;
        }
        return t;
    }
}
复制代码

非递归的实现

int pow(int x, int n, int p) {
    int re = 1;
    for (; n > 0; n >>= 1) {
        if (n % 2 == 1) {
            re = (long long)re * x % p;
        }
        x = (long long)x * x % p;
    }
    return re;
}
复制代码
  1. 一般使用非递归版本较多。
  2. 在数论和计数问题中,一般认为0^0 = 1.
  3. n为负数,会导致程序出错。
  4. 如果p = 1, n = 0,那么函数会返回1,而应该返回0,除了极少数坑人的题目,一般并不需要考虑这种情况。
  5. 如果p的范围超过了int,那么需要额外实现一个long long * long long % long long 的函数,见推广部分。
  6. x可能大于p,在一些情况中,这会导致第一次x * x % p越界。
  7. 如果不考虑代码风格,可以把(n % 2 == 1)替换为(n & 1)

其他语言

Python语言中的pow函数,可以直接计算整数的快速幂,非常适合用来做手速题。(其实你只需要在自己的模板中实现出快速幂即可)

推广

在一些情况中快速幂的n可能非常大。

  1. 如果n是输入的高精度数字(一般为十进制)那么并不需要进行每次模二,除以二的快速幂;可以用十进制快速幂。
  2. 如果是通过其他方式计算的,比如希望计算pow(x, pow(y, n), p)可以先通过找循环节的方式把指数部分取模pow(x, pow(y, n), p) == pow(x, pow(y, n, p - 1), p)

这个算法对于所有有结合律的运算均可以优化,其他常见的如下

  1. 整数
  2. 矩阵
  3. 多项式
  4. 排列/置换

题目

luogu P1226

快速幂模板题,但是需要考虑很多特殊情况。

bzoj 4475

答案是pow(2, k * n, p)

参考资料

Wikipedia Exponentiation by squaring



无旋式Treap

介绍

\text{Treap} 是一种基于旋转操作的平衡树。它给每个结点随机分配一个优先级,使得整棵树从权值上看是二叉查找树,从优先级上看是一个堆,来做到期望 O(\log n) 的深度。​

无旋式 \text{Treap} 使用了 \text{Treap} 随机化的思想,但用 mergesplit 操作代替了旋转,因此比旋转式 \text{Treap} 拥有了更强大的功能,比如提取区间和快速合并,分裂。

基本算法

首先做一些定义,若 x 表示某结点,则 left(x),right(x) 表示 x 的左右孩子,size(x) 表示 x 的子树大小,val(x) 表示 x 的权值,pri(x) 表示 x 的随机优先级(越小越优先,即从 pri 上看 \text{Treap} 是一个小根堆)。

无旋式 \text{Treap} 的两个基本操作是 mergesplit

merge(a,b) 表示合并两个 \text{Treap} : a,b ,满足 a 中所有结点的 val 都比 b 中的小。返回值是合并后的 \text{Treap}

考虑如何实现 merge(a,b) 。 首先如果 a,b 有一个为空,那么返回另一个即可。 否则 若 pri(a)<pri(b) ,那么说明 a 是合并后的根,那么令 a 的左儿子不变,右儿子变成 merge(right(a),b) ,返回 a ; 否则 b 是合并后的根,那么令 b 的右儿子不变,左儿子变成 merge(a,left(b)) ,返回 b

每次递归都有一个结点变成其某个儿子,所以 merge 的复杂度显然就是 a,b 树高之和。

split 分两种,一种是按权值大小分,一种是按结点个数分。


先讲按权值大小分。 split\_v(a,v) 表示给定一个 \text{Treap}:a ,返回两个 \text{Treap}:\{l,r\} ,分别包含 a 中权值 \le v 的结点和权值 >v 的结点。

考虑如何实现 split\_v(a,v) 。 首先如果 a 为空,那么直接返回 {空,空}即可。 否则 如果 a 的权值 \le v ,那么令 \{l_1,r_1\}=split\_v(right(a),v) ,将 a 的右儿子变成 l_1 ,返回 \{a,r_1\} 即可; 否则 a 的权值 >v ,那么类似的,令 \{l_1,r_1\}=split\_v(left(a),v) ,将 a 的左儿子变成 r_1 ,返回 \{l_1,a\} 即可。

每次递归 a 都会变成其某个儿子,因此 split\_v 的复杂度显然就是 a 的树高。


按结点个数分是类似的。 split\_sz(a,k) 表示给定一个 \text{Treap}:a ,返回两个 \text{Treap}:\{l,r\}l 中包含 a 中权值前 k 小的结点,r 中包含其他结点。

split\_sz(a,k) 的实现是类似的。 首先如果 a 为空(这时 k 一定为 0 ),那么直接返回 {空,空}即可。 否则 如果 k \le size(left(a)),那么令 \{l_1,r_1\}=split\_sz(left(a,k)) ,将 a 的左儿子变成 r_1 ,返回 \{l_1,a\} 即可。 否则 k>size(left(a)) ,那么令 \{l_1,r_1\}=split\_sz(right(a),k-(size(left(a))+1)) ,将 a 的右儿子变成 l_1 ,返回 \{a,r_1\} 即可。

复杂度同样也是 a 的树高。


有了 mergesplit 这两个基本操作,就可以实现很多功能。

比如如果要提取一段区间的结点,就可以通过两次 split\_sz 得到,操作完了再通过两次 merge 变回去。

代码

模板题:https://www.luogu.org/problemnew/show/P3369

实现基本操作后其他操作都非常简单,好写。

#include<bits/stdc++.h>
using namespace std;

int rand_int()
{
	return RAND_MAX<=32768?rand()*32768+rand():rand();
}
struct Node
{
	Node *cl,*cr;
	int sz,val,pri;
	Node (int v)
	{
		cl=cr=0;
		sz=1;
		val=v;
		pri=rand_int();
	}
	void push_up()
	{
		sz=(cl?cl->sz:0)+(cr?cr->sz:0)+1;
	}
};
Node *merge(Node *a,Node *b)
{
	if(!a)return b;
	if(!b)return a;
	if(a->pri<b->pri)
	{
		a->cr=merge(a->cr,b);
		a->push_up();
		return a;
	}
	else
	{
		b->cl=merge(a,b->cl);
		b->push_up();
		return b;
	}
}
void split_v(Node *a,int v,Node *&l,Node *&r)
{
	if(!a){l=r=0;return ;}
	if(a->val<=v)
	{
		split_v(a->cr,v,l,r);
		a->cr=l;
		l=a;
	}
	else
	{
		split_v(a->cl,v,l,r);
		a->cl=r;
		r=a;
	}
	a->push_up();
}
void split_sz(Node *a,int k,Node *&l,Node *&r)
{
	if(!a){l=r=0;return ;}
	int sz_l=a->cl?a->cl->sz:0;
	if(k<=sz_l)
	{
		split_sz(a->cl,k,l,r);
		a->cl=r;
		r=a;
	}
	else
	{
		split_sz(a->cr,k-sz_l-1,l,r);
		a->cr=l;
		l=a;
	}
	a->push_up();
}
Node *rt;
void insert(int v)
{
	Node *now=new Node(v),*l,*r;
	split_v(rt,v,l,r);
	rt=merge(merge(l,now),r);
}
void erase(int v)
{
	Node *l,*r;
	split_v(rt,v-1,l,r);
	Node *r1,*r2;
	split_sz(r,1,r1,r2);
	rt=merge(l,r2);
}
int rank_of_key(int v)
{
	Node *l,*r;
	split_v(rt,v-1,l,r);
	int ans=(l?l->sz:0)+1;
	rt=merge(l,r);
	return ans;
}
int key_of_rank(int k)
{
	Node *l,*r;
	split_sz(rt,k-1,l,r);
	Node *r1,*r2;
	split_sz(r,1,r1,r2);
	int ans=r1->val;
	rt=merge(l,merge(r1,r2));
	return ans;
}
int find_pre(int v)
{
	Node *l,*r;
	split_v(rt,v-1,l,r);
	Node *l1,*l2;
	split_sz(l,l->sz-1,l1,l2);
	int ans=l2->val;
	rt=merge(merge(l1,l2),r);
	return ans;
}
int find_suf(int v)
{
	Node *l,*r;
	split_v(rt,v,l,r);
	Node *r1,*r2;
	split_sz(r,1,r1,r2);
	int ans=r1->val;
	rt=merge(l,merge(r1,r2));
	return ans;
}

int main()
{
	int n;
	cin>>n;
	while(n--)
	{
		int op,x;
		scanf("%d%d",&op,&x);
		if(op==1)insert(x); else
		if(op==2)erase(x); else
		if(op==3)printf("%d\n",rank_of_key(x)); else
		if(op==4)printf("%d\n",key_of_rank(x)); else
		if(op==5)printf("%d\n",find_pre(x)); else
			printf("%d\n",find_suf(x));
	}
}
复制代码

题目

\text{Splay} 的题基本上都可以用无旋式 \text{Treap} 做。

https://www.luogu.org/problemnew/show/P3391

https://www.luogu.org/problemnew/show/P1110

https://www.luogu.org/problemnew/show/P2596

https://www.luogu.org/problemnew/show/P2286

https://www.luogu.org/problemnew/show/P2042

参考资料

《可持久化数据结构研究》,陈立杰

转载于:https://juejin.im/post/5c7897006fb9a049b50782f6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值