Acwing 第一章模板及详解(基础算法)

一、排序
二、二分
三、高精度
四、前缀和与差分
五、位运算
六、双指针算法
七、离散化
八、区间合并

一、排序


(一)快速排序算法:

由冒泡排序改进,在冒泡排序过程中,只对相邻的两个记录进行比较,每次交换只能消除一个逆序;而快速排序时对不一定相邻的两个记录进行比较,每次交换可以消除多个逆序。
快速排序是不稳定的。
时间复杂度:

最差:O(n^2);

平均:O ( n l o g 2 n ) 。

1.确定分界点  q[l]  ,  q[(l+r)/2]  ,  q[r];    //随机选取

2.调整区间,使小于等于x的在左边 l,大于等于x 的在右边 r;

3.递归处理x的左右段;

定义指针  i  ,j   使得   i,   j 都向中间读取值,当   i   的读取值大于x,并且  j  的的值小于x,交换两个数的值,直到  i,  j 两个数相等。

如果left>=right,则退出;
在数组中随即确定一个分界点x,假设x=(left+right)/2;
设指针i,j,分别指向left,right;
当i<j时,循环执行5,6,7步;
如果i指针所指向的数小于x,则i++;否则,跳出循环;
如果j指针所指向的数大于x,则j--;否则,跳出循环;
如果i<j,则swap(arr[i],arr[j]);
调整区间,递归处理左右两段,quick_sort(arr,left,i);quick_sort(arr,i+1,right);

 

模板 :

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}


(二)归并排序算法

1.确定分界点,(r+l)/2=mid;

2.递归排序左右两边;    //两个有序的序列

3.归并,合二为一;
归并排序是稳定的。
时间复杂度:O ( n l o g 2 n ) 。

分成两段,比较min1,min2,去更小值min3,放到一个新的序列中,指针依次往后走,直到一个序列排完,然后将剩余的序列放到后面, 依次进行。

模板 :

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

二、二分:

  1. 首先对数组进行排序。
  2. 将要查找值x与数组中间值mid相比较;
  3. 如果x=mid,则找到x,退出程序;
  4. 如果x<mid,则将边界设为[left,mid];
  5. 如果x>mid,则将边界设为[mid+1,right];
  6. 循环执行4,5两步骤,若直到数组长度为1,还没找打x,则数组中不存在x。


整数二分算法:

选择答案所在区域,确定使用哪个模板

模板:

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}


浮点数二分算法模板 

1.保留小数问题的情况下,往后提2个精度

2.一般情况下,可以用for(int i=0;i<100;i++)代替;

模板:

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

三、高精度

加减法:
1.用字符串读入两个大整数a,b;
2.用vector<int> 分别倒序存储a,b,得到A,B,设和数向量C;
3.如果是减法则执行步骤6;
4.设中间变量t来存储进位,t分别加A[i],B[i],将t%10 push到C,令t/=10;
5.循环执行4,直到i大于A,B两向量的长度。
6.首先根据A,B的长度,高位的值比较大小;
7.设中间变量t来存储进位,A[i]-B[i]-t,将(t+10)%10 push到C,若t<0,则令t=1,否则令t=0;
乘除法:
1.用字符串读入大整数a,整型读入整数b;
2.用vector<int> 倒序存储a,得到A,设向量C;
3.如果是除法则执行步骤6;
4.设中间变量t来存储进位,t加A[i]*b,将t%10 push到C,令t/=10;
5.循环执行4,直到i大于A的长度。
6.设余数r,r=r*10+A[i],将r/bpush到C,令r%=b;
7.将C中的值反转,去除前置0。


高精度的常见场景:

两个比较大的整数相加 A+B,位数约为10的6次方
两个比较大的整数相减 A+B,位数约为10的6次方
一个大整数乘一个小整数 A*a,A的位数<=10的6次方,a的数值<=10的9次方
一个大整数除以一个小整数 A/a
大整数是如何存储的呢?其实是把大整数的每一位存入数组里面。


数组下标为0的位置存储最低位。

为什么?

因为存在进位的可能,那么就需要在最高位补上一个数。而在数组的最末尾是最容易增加数据的。(push_back)

高精度加法
运算是模拟人工加法的过程。

先相加个位,得出结果和进位。
用代码模拟:Ai+Bi+进位t(0或者1)

(1.)存储数字:123456789

数组:0 1 2 3 4 5 6 7 8

           9 8 7 6 5 4 3 2 1     //位数倒存

  (2.)   不存在A或B时,将其当作0.

将  t  当作进位

高精度加法 模板:

// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B) //数组A   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;
}

高精度的输入方法:

int main()
{
	string a,b;
	vector <int >A.B;
	cin>>a>>b;   //输入“123456”
	for(int i=a.size()-1;i>-0;i--) A.push_back(a[i]-'0')
	for(int i=b.size()-1;i>-0;i--) B.push_back(a[i]-'0')//遍历之后的A={6,5,4,3,2,1};
	auto C=add(A,B);   // auto 自行判断数据的类型,类似于vector<int>
	for(int i=c.size()-1;i>-0;i--) printf("%d",c[i]);
	return 0;	
	
} 


高精度减法 
如果一个位够减就直接减
低位如果不够减,就需要向高位借位

模板:

// 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 用来借位
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];  //判断B是否有这一位
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();  // 去掉结果前缀和前面的0
    return C;
}

判断计算c-a-b时要先判断是否有a>=b

bool cmp(vector<int>&A,vector<int>&B)
{
	if(A.size()!=B.size()) return A.size()>B.size();
	for(int i=a.size()-1;i>-0;i--) 
	if(A{i}!=B[i])   return A[i]>B[i];
	return ture;  
}
if(cmp(A,B))
{
	auto c=sub(A,B)
	for(int i=c.siz()-1;i>=0;i--)  printf("%d",c[i]);
}
else 
{
	auto c=sub(B,A);
	printf("-");
	for(int i=c.size()-1;i.=0;i--)
	printf("%d",c[i]);
}
return 0;


高精度乘低精度 

模板:

// 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();  //去掉前缀和中的0

    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;
}

四、前缀和与差分

(一)前缀


一维前缀和

作用:

能快速地求出原数组里一段数的和。可以只用一次运算就求出来任意区间的一段和。

前缀和可以快速地求出原数组里从l到r,即区间[l,r]这一段数的和 。

如果没有前缀和数组,就要循环一遍,时间复杂度为O(n);如果有前缀和数组,只需要s[r]-s[l-1],时间复杂度为O(1)

思想:

  1. 前缀和数组,原数组的下标均由1开始。
  2. 求前缀和:将S[0]置0,S[i]=S[i-1]+a[i]
  3. 区间[l,r]的数组的和等于S[r]-S[l-1]

模板:

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

二维前缀和 

  1. 先求前缀和:
  2. 求子矩阵的和:

模板:

S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

(二)差分:

若a[n]是b[n]的前缀和,则b[n]是a[n]的差分。
使区间[l,r]中的每一个a[i]+c,则只需b[l]+cb[r+1]-c

思想:

  1. 假设a数组初始值全为0,则b数组的值也全为0。
  2. 将a[i]的值插入a数组,则可以理解为在区间[i,i]中a[i]+c,那么需对b[i]+a[i],b[i+1]-a[i]
  3. 最终对b[i]求前缀和即可。

原数组:

    a1,a2,a3,a4,a5,a6.......an;

构造数组:

   b1,b2,b3,b4,b5,b6......bn;

使得:

   an=b1+b2+b3+b4+......bn;

b称为a的差分,a称为b的前缀和;

b1=a1;

b2=a2-a1;

b3=a3-a2;

.

bn=an-an-1;

区间[l,r],   al后面到ar 加上c,ar后面的不加c;


一维差分 

模板:

给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c


二维差分

模板:

给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c


位运算(二进制中)

  1. 求n的二进制表示中第k+1位是几,n>>k&1。
  2. 返回x的最后一位1是多少,x&-x,x=1010,lowbit(x)=2。(求x的二进制中多少个1)

       把k位移到最后一位 n>>k

       看个位是多少 x&1

求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n


双指针算法

  1. 设一个计数数组S[n],记录在[i,j]区间内数每个数出现的次数。
  2. i每向右移一位,S[a[i]]++;j向右移之前,先S[a[j]]–。
  3. 记录不重复子数组的最长长度。

模板:

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}


常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作


离散化

  1. 用到前缀和求区间内值的和。
  2. 去重:先将数组进行排序,利用unique,erase去重。
  3. 二分查找。

常见问题:

1.a[]中可能有重复元素;

2.如何算出a[]离散后的值;

模板:

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

在x 的下标加上c 

vector<PII>add,query;

add.push_back({x,c});


区间合并 

  1. 按照区间左端点排序。
  2. 初始化左st右ed边界为-2e9。
  3. 比较ed与当前所在区间左端点,更新区间,并将更新后的区间记录下来。
  4. 返回数组长度。

模板:

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值