acwing算法基础课第一章模板

一、 快速排序(QuickSort)

算法思想:

由冒泡排序改进,在冒泡排序过程中,只对相邻的两个记录进行比较,每次交换只能消除一个逆序
而快速排序时对不一定相邻的两个记录进行比较,每次交换可以消除多个逆序
快速排序是不稳定的
时间复杂度:最差: O ( n 2 ) O(n^2) O(n2);平均: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

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

代码:

#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e6+10;
int n;
int q[N];
void quick_sort(int q[],int l,int r)
{
	if(l >= r) return;
	int x = q[l],i = l-1,j = r+1;
	while(i < j)
	{
		//如果12 12不先进行++,--操作的话,会造成死循环
		do i++; while(q[i] < x);
		do j--; while(q[j] > x);
		if(i < j) swap(q[i],q[j]);
	}
	//会一直存在(0,i)区间,死循环
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&q[i]);
	quick_sort(q,0,n-1);
	for(int i=0;i<n;i++)
		printf("%d ",q[i]);
	
	return 0;
}

二、 归并排序(MergeSort)

分治法思想
归并排序是稳定的
时间复杂度: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

算法思想:

  1. 取数组的中间值作为分界点mid;
  2. 将mid左边的序列,右边的序列分别排好序;
  3. 对左右序列进行合并。

代码:

#include <iostream>
#include <stdio.h>
using namespace std;

const int N = 1e6+10;
int q[N],tmp[N];
int n;

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 <= mid)
        tmp[k++] = q[j++];

    for(i=l,j=0;i<=r;i++,j++)
        q[i] = tmp[j];
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&q[i]);

	merge_sort(q,0,n-1);

	for(int i=0;i<n;i++)
		printf("%d ",q[i]);

	return 0;
}

三、 二分查找(BinarySearch)

算法思想:

  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。

代码:

//整数二分查找
#include <iostream>
#include <stdio.h>
using namespace std;

const int N = 1e6+10;
int q[N];
int n,m;


int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
		scanf("%d",&q[i]);

	while(m--)
    {
        int x;
        scanf("%d",&x);

        int l=0,r=n-1;
        //取第一个等于x的数,右边界等于mid
		while(l<r)
        {
            int mid = l+r >> 1;
            if(x <= q[mid])
                r = mid;
            else
                l = mid + 1;
        }
        if(q[l] != x)
            cout << "-1 -1" << endl;
        else
        {
            cout << l << " ";
            int l=0,r=n-1;
            //取最后一个等于x的数,左边界等于mid
            while(l<r)
            {
                int mid = l+r+1 >> 1;
                if(x >= q[mid])
                    l = mid;
                else
                    r = mid -1;
            }
            cout << l << endl;
        }
    }

	return 0;
}
//浮点数二分查找
#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
	double x;
	scanf("%lf",&x);
	double l = 0,r = x;
	while(r-l > 1e-8)
    {
        double mid = (l+r)/2;
        if(mid*mid >= x)
            r = mid;
        else
            l = mid;
    }
    printf("%lf\n",l);

	return 0;
}

四、 高精度算法

存储: 数组存储,低位数存在低位。
算法思想:

  • 加减法:
    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。

代码:
加法,减法:

#include <iostream>
#include <vector>
using namespace std;

//大整数加法
vector<int> add(vector<int> &A,vector<int> &B)
{
    vector<int> C;
    int t = 0;
    for(int i=0;i<A.size()||i<B.size();i++)
    {
        if(i < A.size())
            t += A[i];
        if(i < B.size())
            t += B[i];
        C.push_back(t%10);
        t /= 10;
    }
    if(t)
        C.push_back(1);
    return C;
}

//大整数减法
bool cmp(vector<int> &A,vector<int> &B)
{
    if(A.size() != B.size())
        return A.size()>B.size();
    else
    {
        for(int i=A.size()-1;i>=0;i--)
            if(A[i] != B[i])
                return A[i] > B[i];
        return true;
    }
}

vector<int> sub(vector<int> &A,vector<int> &B)
{
    vector<int> C ;
    int t = 0;
    for(int i=0;i<A.size();i++)
    {
        if(B.size()<i)
            t = A[i]-t;
        else
            t = A[i] - B[i] - t;
         C.push_back((t+10)%10);
         if(t<0)
            t = 1;
         else
            t = 0;
    }
	//去掉前导0
    while(C.size()>1 && C.back()==0)
        C.pop_back();
    return C;
}


int main()
{
	string a,b;
    vector<int> A,B;

	cin >> a >> b;
	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(b[i]-'0');

    vector<int> C = add(A,B);

    for(int i=C.size()-1;i>=0;i--)
        cout << C[i];
    cout << endl;
    if(cmp(A,B))
        C = sub(A,B);
    else
    {
    	C = sub(A,B);
		cout << "-";
	}
    for(int i=C.size()-1;i>=0;i--)
        cout << C[i];

	return 0;
}

乘法、除法:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> mul(vector<int> &A,int b)
{
    vector<int> C;
    int t = 0; //进位
    for(int i=0;i<A.size();i++)
    {
        t += A[i]*b;
        C.push_back(t%10);
        t /= 10;
    }
    if(t)
        C.push_back(t);
    return C;
}

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


int main()
{
	string a;
	int b;
    vector<int> A;

	cin >> a >> b;
	for(int i=a.size()-1;i>=0;i--)
        A.push_back(a[i]-'0');

    vector<int> C = mul(A,b);

    for(int i=C.size()-1;i>=0;i--)
        cout << C[i];
    cout << endl;

    int r;
    C = div(A,b,r);

    for(int i=C.size()-1;i>=0;i--)
        cout << C[i];
    cout << " " << r;

	return 0;
}

五、 一维前缀和

算法思想:

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

代码:

#include <iostream>
using namespace std;

const int N = 100010;
int a[N],s[N];
int m,n;

int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++)
        cin >> a[i];

    for(int i=1;i<=n;i++)
        s[i] = s[i-1] + a[i];

    while(m--)
    {
        int l,r;
        cin >> l >> r;
        cout << s[r] - s[l-1] << endl;
    }
	return 0;
}

六、 子矩阵前缀和

算法思想:

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

代码:

#include <iostream>
using namespace std;

const int N = 1010;
int a[N][N],s[N][N];
int m,n,q;

int main()
{
    cin >> n >> m >> q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin >> a[i][j];

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];

    while(q--)
    {
        int x1,x2,y1,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]<< endl;
    }
	return 0;
}

七、 差分

若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]求前缀和即可。

代码:

#include <iostream>
using namespace std;

const int N = 100010;
int a[N],b[N];
int m,n;

void inserta(int l,int r, int c)
{
    b[l] += c;
    b[r+1] -= c;
}

int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++)
        cin >> a[i];

    for(int i=1;i<=n;i++)
        inserta(i,i,a[i]);

    while(m--)
    {
        int l,r,c;
        cin >> l >> r >> c;
        inserta(l,r,c);
    }

    for(int i=1;i<=n;i++)
        b[i] += b[i-1];

    for(int i=1;i<=n;i++)
        cout << b[i] << " ";

	return 0;
}

八、 差分矩阵

算法思想:
同上思想相同。

代码:

#include <iostream>
using namespace std;

const int N = 1010;
int a[N][N],b[N][N];
int m,n,q;

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

int main()
{
    cin >> n >> m >> q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin >> a[i][j];
            inserta(i,j,i,j,a[i][j]);
        }

    while(q--)
    {
        int x1,x2,y1,y2,c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        inserta(x1,y1,x2,y2,c);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            cout << b[i][j]  << " ";
        cout << endl;
    }


	return 0;
}

九、 双指针算法

算法思想:

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

代码:

#include <iostream>
using namespace std;

const int N = 100010;
int a[N],s[N];

int main()
{
    int n;
    cin >> n;
    for(int i=0;i<n;i++)
        cin >> a[i];

    int res = 0;
    for(int i=0,j=0;i<n;i++)
    {
        s[a[i]]++;
        while(s[a[i]] > 1)
        {
            s[a[j]]--;
            j++;
        }
        res = max(res,i-j+1);
    }

    cout << res << endl;

	return 0;
}

十、 位运算

算法思想:

  1. 求n的二进制表示中第k+1位是几,n>>k&1。
  2. 返回x的最后一位1是多少,x&-x,x=1010,lowbit(x)=2。(求x的二进制中多少个1)
    代码:
#include <iostream>
using namespace std;

int lowbit(int x)
{
    return x&-x;
}

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int x;
        cin >> x;

        int res = 0;
        while(x)
        {
            x -= lowbit(x);
            res++;
        }

        cout << res << " ";
    }

	return 0;
}

十一、 离散化

算法思想:

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

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef pair<int,int> p;
const int N = 300010;
int n,m;
int a[N],s[N];

vector<int> alls;
vector<p> query,add;

int findf(int 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;
}


int main()
{
    cin >> n >> m;
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin >> x >> c;
        add.push_back({x,c});

        alls.push_back(x);
    }

    for(int i=0;i<m;i++)
    {
        int l,r;
        cin >> l >> r;
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }

    //去重
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());

    //处理插入
    for(int i=0;i<add.size();i++)
    {
        int x = findf(add[i].first);
        a[x] += add[i].second;
    }

    //预处理前缀和
    for(int i=1;i<=alls.size();i++)
        s[i] =  s[i-1] + a[i];

    //处理询问
    for(int i=0;i<query.size();i++)
    {
        int l = findf(query[i].first);
        int r = findf(query[i].second);
        cout << s[r] - s[l-1] << endl;
    }

	return 0;
}

十二、 区间合并

算法思想:

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

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef pair<int,int> p;
const int N = 100010;

int n;
vector<p> seg;

void mergea(vector<p> &seg)
{
    vector<p> res;
    sort(seg.begin(),seg.end());

    int st = -2e9, ed = -2e9;
    for(int i=0;i<seg.size();i++)
    {
        if(ed < seg[i].first)
        {
            if(st != -2e9)
                res.push_back({st,ed});
            st = seg[i].first;
            ed = seg[i].second;
        }
        else
            ed = max(ed,seg[i].second);
    }
    if(st != -2e9)
        res.push_back({st,ed});
    seg = res;
}

int main()
{
    cin >> n;

    for(int i=0;i<n;i++)
    {
        int l,r;
        cin >> l >> r;
        seg.push_back({l,r});
    }

    mergea(seg);

    cout << seg.size() << endl;

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值