基础算法

基础算法

基础杂项

排序

快排

快排板子

核心思想: 分治

const int N = 1e5+5;
int g[N];
int quick_sort(int l, int r)
{
	int p=l, q=r, x=g[l+r >> 1];
	while(p<=q)
	{
		while(g[p] < x) p++;
		while(g[q] > x) q--;
		if(p<=q) swap(g[p], g[q]), p++, q--;
	}
	if(q<r) quick_sort(q, r);
	if(p>l) quick_sort(l, p);
}

复杂度为\(O(nlgn)\)

第k大数
const int N = 1e5+5;
int g[N];
int kth_num(int l, int r, int k)
{
	int p=l, q=r, x=g[l+r >>1];
	while(p<=q)
	{
		while(g[p] < x) p++;
		while(g[q] < x) q--;
		if(p<=q) swap(g[p], g[q]), p++, q--;
	}
	if(p<=k) return kth_num(p, r); //停在了第p个位置,比第k个要小,结果在右边
	else if(q>=k) return kth_num(l, q); //不在右边结果就在左边
	else return g[l]; //前二者都不是,在中间.
}

复杂度为\(O(lgn)\)

对顶堆

对于多次询问第k大数,我们可以选择调用刚才的第k大数板子,复杂度为\(O(nlgn)\)
但是也可以用两个堆维护,一个大顶堆一个小顶堆,被称作对顶堆

//查询第k大时,此时保证元素一定足够
//声明
priority_queue<int> bh;//大顶堆
priority_queue<int, vector<int>, greater<int>> sh;//小顶堆
//插入操作
if(!bh.size() || x > bh.top()) sh.push(x);
else bh.push(x);
//查询操作,当前要查询第k大
while(bh.size() > k)
{
	int p = bh.top(); bh.pop();
	sh.push(p);
}
while(bh.size() < k)
{
	int p = sh.top(); sh.pop();
	bh.push(p);
}

对顶堆的复杂度也可近似看作\(O(nlgn)\)
对顶堆在没有删除、每次要查询的第K大数递增时时间常数要优一些.

归并

归并板子
const int N = 1e5+5;
int g[N], b[N]; //b用于备份
void MergeSort(int l, int r)
{
	if(l==r) return; //边界 只有一个元素 无需排序
	int mid = l+r >> 1;
	MergeSort(l, mid); MergeSort(mid+1, r);
	int p=l, q=mid+1, cur=p;
	while(p<=mid && q<=r)
	{
		if(g[p] <= g[q]) b[cur++] = g[p++];
		else b[cur++] = g[q++];
	}
	while(p<=mid) b[cur++] = g[p++];
	while(q<=r) b[cur++] = g[q++];
	for(int i=l; i<=r; i++) g[i] = b[i];
}
逆序对的个数

首先证明逆序对的个数就是归并排序中交换的次数
将[1, n]的问题分解为[1, mid]与[mid+1, n]的问题
如果我们知道两个子问题的解,剩下的部分解就是在左边并且比右边大的元素对数了,这用归并排序很好实现.

const int N = 1e5+5;
int g[N], b[N];
void MergeSort(int l, int r)
{
	int ans = 0;
	if(l==r) return;
	int mid = l+r >> 1;
	MergeSort(l, mid); MergeSort(mid+1, r);
	int p=l, q=mid+1, cur=l;
	//记录右区间的方式
	while(p<=mid && q<=r)
	{
		if(g[p] <= g[q]) b[cur++] = g[p++]; //此处是小于等于
		//是因为小于等于的边界条件,如果是小于的话当两个元素相等坐标
		//q向前移动,此时答案更新,但p这个点也被算了进去.
		else b[cur++] = g[q++], ans += mid - p + 1;
	}
	while(p<=mid) b[cur++] = g[p++];
	while(q<=r) b[cur++] = g[q++];
	//记录左区间的方式
	while(p<=mid && q<=r)
	{
		if(g[p] <= g[q]) b[cur++] = g[p++], ans += q - (mid + 1); 
		//小于等于同理.
		//如果是小于那么会加入q. 下次更新时答案会多出来
		else b[cur++] = g[q++];
	}
	while(p<=mid) b[cur++] = g[p++], ans += q - (mid + 1); //即 r - mid
	while(q<=r) b[cur++] = g[q++];
	for(int i=l; i<=r; i++) g[i] = b[i];
	/*有两种计数方式,更优秀的一种是记录当右区间加入时的
	  这样的计数方式只需记录一次,在后面的循环中无需记录
	  而记录左区间加入时的计数方式需要记录两次.
	*/
}
最大子序列和

四种做法

  1. 暴力

  2. 优化的暴力

  3. 分治

  4. dp
    暴力和优化的暴力见视频 子序列最值

这里分析一下这个题目为什么能用分治

问题[1, n]可以分解为[1,mid]与[mid+1]. 随后的横跨问题也很好解决,所以是天然的分治模板

二分

整数二分

大于等于模板
const int N = 1e5+5;
int g[N];
int l=1, r=n;
int x; //query
while(l<r)
{
	int mid = l + r >> 1;
	if(g[mid] >= x) r = mid;
	else l = mid + 1;
}
小于等于模板
const int N = 1e5+5;
int g[N];
int l=1, r=n;
int x; //query
while(l<r)
{
	int mid = l+r+1 >> 1;
	if(g[mid] <= x) l=mid;
	else r = mid-1;
}

注意小于等于时候要加1防止死循环

浮点数二分

例:
给定一个浮点数n,求它的三次方根。数的三次方根

\[−10000≤n≤10000 \]


double l=-100, r=100;
double n; cin>>n;
while(fabs(l-r) <= 1e-7)
{
    double mid = (l+r)/2;
    if(mid*mid*mid<=n) l=mid;
    else r=mid;
}

浮点数不需要考虑小于等于板子加一的问题,因为有fabs做保证。

高精度

高精度加法

// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }

    if (t) C.push_back(t);
    return C;
}

高精度减法

// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
    vector<int> C;
    for (int i = 0, t = 0; i < A.size(); i ++ )
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度乘法

//高精度乘以低精度
// C = A * b, A >= 0, b > 0
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}
//高精度乘以高精度O(n2),必要时用FFT优化
vector<int> mul(vector<int> A, vector<int> B)
{
    vector<int> C(A.size() + B.size());

    for (int i = 0; i < A.size(); i ++ )
        for (int j = 0; j < B.size(); j ++ )
            C[i + j] += A[i] * B[j];

    for (int i = 0, t = 0; i < C.size() || t; i ++ )
    {
        t += C[i];
        if (i >= C.size()) C.push_back(t % 10);
        else C[i] = t % 10;
        t /= 10;
    }

    while (C.size() > 1 && !C.back()) C.pop_back();

    return C;
}

高精度除法

// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度比大小

vector<int> max_vec(vector<int> a, vector<int> b)
{
	if(a.size() > b.size()) return a;
	if(a.size() < b.size()) return b;
	if(vector<int>(a.rbegin(), a.rend()) > 
	vector<int>(b.rbegin(), b.rend())) return a;
	return b;
}

高精度输出

void output(vector<int> a)
{
	for(int i=a.size()-1; i>=0; i--) cout<<a[i]; 
}

前缀差分

一维

一维前缀和
一维差分

二维

二维前缀和

求前缀和:
\(g[i][j] = g[i][j] + g[i-1][j] + g[i][j-1] - g[i-1][j-1];\)
复原:
x1, y1 到 x2, y2的和:
\(g[x2][y2]-g[x1-1][y2]-g[x2][y1-1]+g[x1-1][y1-1]\)

二维差分

插入操作:

void insert (int x1, int y1, int x2, int y2, int c) 
{
    g[x1][y1] += c;
    g[x1][y2+1] -= c;
    g[x2+1][y1] -= c;
    g[x2+1][y2+1] += c;
}

复原即求一边前缀和

双指针

双指针与其说是一种算法,不如说是一种思维,将问题无用的部份遗弃掉。
这里给出双指针的几个简单例子,更多的例子请见双指针专题。

最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。


位运算

XOR

lowbit

return x & -x;

二进制1的个数

while(p) p&=(p-1), cnt++;

区间问题

离散化

for(int i = 1; i <= n; i++)
	cin >> g[i], alls.push_back(g[i]);
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());

区间合并

void merge(vector<PII> &g)
{
	vector<PII> res;
	sort(g.begin(), g.end());
	int st = -2e9, ed = st;
	for(auto s: g)
	{
		if(s.first > ed)
		{
			if(st != -2e9) res.push_back({st, ed});
			st = s.first, ed = s.second;
		}
		else ed = max(ed, s.second);
	}
	if(st != -2e9) res.push_back({st, ed});
	g = res;
}
posted @ 2021-02-24 02:59  2wx  阅读(39)  评论(0)    收藏  举报