Acwing 算法基础课

一.快速排序

1.快速排序(模版)

#include<iostream>
using namespace std;
int a[100010];
int n;
void quickSort(int a[], int l, int r){
    //如果数组中就一个数,就已经排好了,直接返回。
    if(l >= r) return;
    //选取分界线。这里选数组中间那个数
    int x = a[l + r >> 1];
    int i = l - 1, j = r + 1;
    //划分成左右两个部分
    while(i < j){
        while(a[++i] < x);
        while(a[--j] > x);
        if(i < j){
            swap(a[i], a[j]);
        }
    }
    //对左部分排序
    quickSort(a, l, j);
    //对右部分排序
    quickSort(a, j + 1, r);
}

int main(){
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> a[i];
    }
    quickSort(a, 0, n - 1);
    for(int i = 0; i < n; i++){
        cout << a[i] << " ";
    }
    return 0;
}

异常:

错误代码: 

//初始化i,j
int i=l,j=r;
while(a[i]<x)i++;
while(a[j]>x)j--;

这样初始化会死循环, 如3 2 2

2.第k个数

跟上题一样,只要输出第k个数就好了

#include<iostream>
using namespace std;
int a[100010];
int n,k;
void quickSort(int a[],int l, int r){
    //如果数组中就一个数,就已经排好了,直接返回。
    if(l >= r) return;
    //选取分界线。这里选数组中间那个数
    int x = a[l + r >> 1];
    int i = l-1 , j = r+1 ;
    //划分成左右两个部分
    while(i < j){
        while(a[++i] < x);
        while(a[--j] > x);
        if(i < j){
            swap(a[i], a[j]);
        }
        //cout<<a[0]<<' '<<a[1]<<' '<<a[2];
    }
    //对左部分排序
    quickSort(a, l, j);
    //对右部分排序
    quickSort(a, j + 1, r);
}

int main(){
    cin >> n>>k;
    for(int i = 0; i < n; i++){
        cin >> a[i];
    }
    quickSort(a,0, n - 1);
    
    cout<<a[k-1];
    return 0;
}

二. 归并排序

3.归并排序(模版题)

#include<iostream>
using namespace std;
int q[100010],tmp[100010];
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 <= r) tmp[k ++ ] = q[j ++ ];

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



int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>q[i];
    
    merge_sort(q,1,n);
    
    for(int i=1;i<=n;i++)cout<<q[i]<<' ';
    
    
}

4.逆序对的数量

#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int temp[N];

long long find(int a[], int l, int r){
    if(l >= r) return 0;
    int mid = l + r + 1 >> 1);
    long long res = 0;
    res += find(a, l, mid);//左边逆序对的数量
    res += find(a, mid + 1, r);//右边逆序对的数量

    int i = l, j = mid + 1, k = 0;
    while(i <= mid && j <= r){
        if(a[i] <= a[j]) temp[k++] = a[i++];
        else//a[i]>a[j] 逆序对
        {
            temp[k++] = a[j++];
            res += mid - i + 1;//i~mid之间的数对a[j]都构成逆序对
        }
    }
    while(i <= mid) temp[k++] = a[i++];
    while(j <= r) temp[k++] = a[j++];

    for(i = l,k = 0;i <= r;i++)
        a[i] = temp[k++];
    return res;


}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n;i++){
        cin >> a[i];
    }
    cout << find(a, 0 ,n - 1);
}

三. 二分

模版(向左搜索)

int l=0,r=n-1;
while(l<r)
{
    int mid=l+r>>1;
    if(check())r=mid;
    else l=mid+1;
}

模版(向右搜索)

int l=0,r=n-1;
while(l<r)
{
    int mid=l+r+1;//必须加1,否则会死循环
    if(check())l=mid;
    else r=mid-1;
}

前置知识:lower_bound和upper_bound的应用(头文件:algorithm

lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

在从小到大的排序数组中,

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

#include <algorithm>
#include <iostream>

using namespace std;

int main()
{
    int a[5] = {5, 3, 2, 1, 9};
    sort(a, a + 5);//首先需要排序
    //因为lower_bound返回的是地址,所以需要先减掉a的首地址
    int n1 = lower_bound(a, a + 5, 3) - a;//找到第一个大于或等于3得数的下标
    int n2 = upper_bound(a, a + 5, 3) - a;//找到第一个大于3的数的下标
    cout << n1 << '\n'
         << n2;
}

5.数的范围

STL版本

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

using namespace std;
const int N = 100005;
int n,q,k,x1,x2,t;
int a[N];

int main() {
    cin >> n >> q;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    while (q--) {
        cin >> k;
        //t = *find(a, a + n, k);
        x1 = lower_bound(a, a + n, k) - a;
        x2 = upper_bound(a, a + n, k) - a-1;
        if(x1 == x2 + 1)
            cout << -1 << ' ' << -1 << endl;
        else
            cout << x1 << ' '<< x2 << endl;
    }

    return 0;
}

6.数的三次方根

题目要求结果保存6位小数,保险起见我们开到-8次方

#include <iostream>
using namespace std;

double n;
int main()
{
    cin >> n;
    double l = -10000, r = 10000;
    while (r - l > 1e-8)//相当于l和r至少都是8位小数,两者之差也是8位小数
    {
        double mid = (l + r) / 2;
        if (mid * mid * mid >= n)
            r = mid;
        else
            l = mid;
    }

    printf("%.6lf", l);

    return 0;
}

四.高精度

高精度是为了解决数溢出的算法,运算的结果可能会有几十位,几十位的数如何加减乘除

整体思路:

首先是string字符串读入,string的极限是1024位字符,但真正的读入是1023位,最后一位读入'\n'换行符。然后传入vector结构,最后运算,详细看下列算法。

7.高精度加法

加法是从低位向高位算的,所以字符串倒着存

#include<iostream>
#include<vector>

using namespace std;

const int N=1e6+10;

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

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');
	
	
	auto C=add(A,B);
	
	for(int i=C.size()-1;i>=0;i--)cout<<C[i]; 
	
	return 0;
}

8.高进度减法

减法需要多考虑两个方面,一是要去掉多余的前导0,二是可能会出现负数,要特判一下

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

string a,b;
vector<int>A,B,C;

//判断A,B谁大
bool large(vector<int> &A,vector<int>&B)
{
    if(A.size()!=B.size())return A.size()>B.size();//如果A,B的长度不同,选择长度大的
    
    for(int i=A.size()-1;i>=0;i--)
    {
        if(A[i]!=B[i])return A[i]>B[i];
    }
    return true;
}

//减法
void subtract(vector<int>&A,vector<int>&B)
{
    int t=0;
    for(int i=0;i<A.size();i++)
    {
        t=A[i]-t;
        if(i<B.size())t-=B[i];
        C.push_back((t+10)%10);//这种写法不需要再判断>=0 or <0
        if(t<0)t=1;//当前位不够减,向前借一位
        else t=0;
    }
    while(C.size()>1&&C.back()==0)C.pop_back();//去掉前导0
}
int main()
{
    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');
    
    
    if(large(A,B))//A>B
    {
        subtract(A,B);
        for(int i=C.size()-1;i>=0;i--)cout<<C[i];
    }
    else//B>A, A-B=-(B-A)
    {
        subtract(B,A);
        cout<<"-";
        for(int i=C.size()-1;i>=0;i--)cout<<C[i];
    }
    
    return 0;
}

9.高精度乘法

#include<bits/stdc++.h>

using namespace std;

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;
	
}
int main()
{
	string a;
	int b;
	
	cin>>a>>b;
	
	vector<int >A;
	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];
	return 0;
 } 

10.高精度除法

#include<bits/stdc++.h>
using namespace std;
int N=1e6+10;


vector<int >mul(vector<int >&A,int b,int &r)
{
	//C=A/b
	vector<int >C;
	
	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());//翻转数组 3 2 1-> 1 2 3
	 	 while(C.size()>1&&C.back()==0)C.pop_back();//去掉前导0
	 return C;
	
}
int main()
{
	string a;
	int b;
	
	cin>>a>>b;
	
	vector<int >A;
	for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
	int r;
	
	vector<int> C=mul(A,b,r);
	
	for(int i=C.size()-1;i>=0;i--)printf("%d",C[i]);
	cout<<endl<<r<<endl;
 } 

五.前缀和

11.前缀和(模版题)

#include <iostream>
using namespace std;

int main()
{
    int n, m;
    int p, q;
    int num[10000000], sum[10000000];

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

    sum[1] = num[1];
    for (int i = 2; i <= n; i++)
    {
        sum[i] = sum[i - 1] + num[i];
    }

    while (m--)
    {
        cin >> p >> q;
    //    cout << endl;
        int cnt = sum[q] - sum[p] + num[p];
        cout << cnt<<endl;
    }
   // cout << endl;
    return 0;
}

12.子矩阵的和

#include <iostream>
using namespace std;

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

int main()
{
    int n, m, q;
    cin >> n >> m >> q;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            scanf("%d", &a[i][j]);
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }

    while (q--)
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);

        printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
    }
    return 0;
}

13.差分

前缀和和差分其实就是类似求导和积分之间的关系。

a[] ,b[]两个数组

a[i]=b[1]+b[2]+.....+b[i];//a是b的前缀和数组
b[i]=a[i]-a[i-1];//b是a的差分数组

差分主要是作用于对一段连续区间进行操作,如果是for循环逐一操作的话,时间复杂度是O(n)

但我们对b[l]+n,b[r+1]+n,两个端点加n的话,a[l]~a[r]整个区间都加n,时间复杂度是O(1)

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];      //构建差分数组
    }
    int l, r, c;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;     //将序列中[l, r]之间的每个数都加上c
        b[r + 1] -= c;
    }
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];    //前缀和运算
        printf("%d ", a[i]);
    }
    return 0;
}

14.差分矩阵

 差分矩阵其实与前缀和子矩阵的和类似

差分就是三步走

1.构造差分

2.确定端点

3.进行前缀和运算

​​​​​​这篇文章很详细了https://www.acwing.com/solution/content/27325/icon-default.png?t=N7T8https://www.acwing.com/solution/content/27325/

#include <iostream>
using namespace std;

const int N = 1e3 + 10;
int n, m, q;
int a[N][N], b[N][N];

void insert(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(){
    // ios::sync_with_stdio(false); //提高cin读取速度,不能使用scanf
    scanf("%d%d%d", &n, &m, &q); // n行m列,q次
    for(int i = 1; i <= n; i++){
        for (int j =1; j <= m; j++){
            scanf("%d", &a[i][j]);
        }
    }
    // 构建差分数组
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
        }
    }
    while (q--){
        int x1, y1, x2, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);
    }
    // 二维前缀和
    for(int i = 1; i <= n; i++){
        for (int j =1; j <= m; j++){
           a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
        }
    }
    for(int i = 1; i <= n; i++){
        for (int j =1; j <= m; j++){
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

六:双指针

双指针算法一般有两种形式

//第一种,同时从左边开始遍历

for(int i=0,j=0;i<n;i++)
    //j一直往右边走,一直到满足某种条件时,才更新i


//第二种,i从左边开始,j从右边开始

for(int i=0,j=n;i<j;j-- or i++)

14.最长连续不重复子序列 

#include<iostream>
using namespace std;
const int N=1e5+10;
int count[N];//¼Ç¼ÔÚi֮ǰÿ¸öÊý³öÏֵĴÎÊý
int a[N];//Ä¿±êÊý×é
int main()
{
    int n,ans=0;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    for(int i=0,j=0;j<n;j++)
    {
        count[a[j]]++;//统计a[j]出现的次数
        while(count[a[j]]>1) //a[j]出现次数大于1
        {
            count[a[i]]--;
            i++;
        }
        ans=max(ans,j-i+1);
    }
    cout<<ans<<endl;
    return 0;
}

15. 数组元素的目标和

#include<iostream>
using namespace std;

const int N = 1e5+10;
int a[N],b[N];

int n,m,x;

int main()
{
    cin>>n>>m>>x;
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=0;i<m;i++)cin>>b[i];
    
    for(int i=0,j=m-1;i<n;i++)
    {
       while(j>0&&a[i]+b[j]>x)j--;
       if(a[i]+b[j]==x)
       {
           cout<<i<<' '<<j<<endl;
           return 0;
       }
        
    }
}

16.判断子序列

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

const int N=100010;

int n,m;
int a[N],b[N];

int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)cin>>a[i];
	for(int i=0;i<m;i++)cin>>b[i];
	
	int i=0,j=0;
	while(i<n&&j<m)
	{
		if(a[i]==b[j])i++;
		j++;	
	}
	if(i==n)puts("Yes");
	else puts("No");
	
	return 0;
}

七:位运算

17. 二进制中1的个数

#include<iostream>
using namespace std;

int n;
int cnt[100010],a[100010];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    
    for(int i=1;i<=n;i++)
    {
        int k=a[i];
        while(k)//位运算
        {
            if(k&1)cnt[i]++;//k&1表示k的二进制数的末尾与1,1&1=1,0&1=0
            k=k>>1;//k的二进数右移一位
        }
        cout<<cnt[i]<<' ';
    }
    
}

八:区间合并

18.区间合并(模版题)

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

#define x first
#define y second
typedef pair<int, int> PII;

PII s[100100];

int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i].x>>s[i].y;
    }
    sort(s+1,s+n+1);
    
    int l=s[1].x,r=s[1].y;
    int res1=0,res2=0,cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(s[i].x<=r)r=max(r,s[i].y);
        else
        {
            l=s[i].x,r=s[i].y;
            cnt++;
        }
       // cout<<cnt<<endl;
        //cout<<l<<' '<<r<<endl;
    }
    cout<<cnt+1<<endl;
    
    return 0;
    
}

九:离散化

19.区间和

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

using namespace std;
const int N = 300010; //n次插入和m次查询相关数据量的上界
int n, m;
int a[N];//存储坐标插入的值
int s[N];//存储数组a的前缀和
vector<int> alls;  //存储(所有与插入和查询有关的)坐标
vector<pair<int, int>> add, query; //存储插入和询问操作的数据

int find(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() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int x, c;
        scanf("%d%d", &x, &c);
        add.push_back({x, c});
        alls.push_back(x);
    }
    for (int i = 1; i <= m; i++) {
        int l , r;
        scanf("%d%d", &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());
    //执行前n次插入操作
    for (auto item : add) {
        int x = find(item.first);
        a[x] += item.second;
    }
    //前缀和
    for (int i = 1; i <= alls.size(); i++) s[i] = s[i-1] + a[i];
    //处理后m次询问操作
    for (auto item : query) {
        int l = find(item.first);
        int r = find(item.second);
        printf("%d\n", s[r] - s[l-1]);
    }

    return 0;
}

十.单链表

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

const int N=100010;

int head,e[N],ne[N],idx;

//初始化
void init()
{
	head=-1;
	idx=0;
 }
 
 //头插法
 void add_to_head(int x)
 {
 	e[idx]=x;
 	ne[idx]=head;
 	head=idx;
 	idx++;

  }
  
  
//尾插法
void add(int k,int x)
{
	e[idx]=x;
	ne[idx]=ne[k];
	ne[k]=idx++;
 } 
 
  
//删除k后面的数
void remove(int k) 
{
	ne[k]=ne[ne[k]];
}

int main()
{
	int m;
	cin>>m;
	
	init();
	
	while(m--)
	{
		int k,x;
		char op;
		
		cin>>op;
		if(op=='H')
		{
			cin>>x;
			add_to_head(x);
		}
		else if(op=='D')
		{
			cin>>k;
			if(!k)head=ne[head];
			remove(k-1);
		}
		else if(op=='I')
		{
		cin>>k>>x;
		add(k-1,x);
		}
	}
	
	for(int i=head;i!=-1;i=ne[i])cout<<e[i]<<" ";
	
	cout<<endl;
	
	return 0;
}

十一:双链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值