算法总结合集

基础算法

快速排序

题目链接:https://www.acwing.com/problem/content/787/

分治,归并思想

#include<iostream>
using namespace std;

const int N=1e6;
int q[N];
void quick_sort(int q[],int l,int r)
{
    if(l>=r)return;
    int x=q[(l+r)/2],i=l-1,j=r+1;
    while(i<j)
    {
        while(q[++i]<x);
        while(q[--j]>x);
        if(i<j)swap(q[i],q[j]);
    }
    quick_sort(q,l,j);
    quick_sort(q,j+1,r);
}
int main()
{
    int n;
    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;
}

快速选择算法

应用:输出第k小的数

题目链接:https://www.acwing.com/problem/content/788/

与快排思想差不多,多了个剪枝

#include <iostream>

using namespace std;

const int N = 1000010;

int q[N];

int quick_sort(int q[], int l, int r, int k)
{
    if(l>=r)return q[l];
    
    int x=q[(l+r)/2],i=l-1,j=r+1;
    while(i<j)
    {
        while(q[++i]<x);
        while(q[--j]>x);
        if(i<j)swap(q[i],q[j]);
    }
    int len=j-l+1;
    if(len>=k)return quick_sort(q,l,j,k);
    else return quick_sort(q,j+1,r,k-len);
}

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);

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

    cout << quick_sort(q, 0, n - 1, k) << endl;

    return 0;
}

归并排序

#include <iostream>

using namespace std;

const int N = 1e6 + 10;//数组大小

int a[N], tmp[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()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    merge_sort(a, 0, n - 1);

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

    return 0;
}

使用归并排序寻找逆序对

题目链接

#include<iostream>
using namespace std;

const int N=1000005;
int q[N],tmp[N];
long long res=0;
int n;

void merge_sort(int l,int r)
{
    if(l>=r)return;//循环终止条件
    int mid=l +  r >> 1; //中间索引
    merge_sort(l,mid),merge_sort(mid+1,r);//对左右两半进行归并
    
    //具体归并步骤
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r){
        if(q[i]<=q[j])tmp[k++]=q[i++];
        else{
            tmp[k++]=q[j++];
            res+=mid-i+1;
        }
    }
    //把没有放入数组的数放入数组
    while(i<=mid)tmp[k++]=q[i++];
    while(j<=r)tmp[k++]=q[j++];
    //返回元素组
    for(int i=l,k=0;i<=r;i++,k++){
        q[i]=tmp[k];
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)cin>>q[i];
    merge_sort(0,n-1);
    cout<<res<<endl;
    return 0;
}

二分法

模板

第二个模板之所以要+1是为了防止mid=l的情况,陷入死循环。

//区间[l.r]被划分成了[l,mid]和[mid+1,r]时使用
int bsearch_1(int l,int r)
{
    while(l<r)
    {
        int mid = l+r >> 2;
        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 >> 2;//这里要+1
        if( check(mid) ) l = mid;//check判断mid左边是否满足条件
        else r=mid-1;
    }
    return l;
}

数的范围

题目链接

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

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    while(m--)
    {
        int x;
        scanf("%d",&x);
        int l=0,r=n-1;
        while(l<r)
       {
        //[l,mid] [mid+1,r]
        int mid= l+r>>1;
        if(a[mid]>=x)r=mid;
        else l=mid+1;
        }
        if(a[l]!=x)cout << "-1 -1" << endl;
        else{
            cout<< l << ' ';
            r=n-1;l=0;
            while(l<r)
            {
               
                //[l,mid-1] [mid,r]
                int mid=l + r + 1 >> 1;
                if(a[mid]<=x)l=mid;
                else r=mid-1;
            }
            cout<<l<<endl;
        }
        
       
        
    }
    return 0;
    
    
}

题目链接

数的三次方

题目链接

#include<iostream>
using namespace std;

int main()
{
    double x;
    scanf("%lf",&x);
    double l=-1000;
    double r=1000;
    while(r-l>1e-8){//将答案精确多个几位更保险
        double mid=(l+r)/2;
        if(mid*mid*mid>x)r=mid;
        else l=mid;
    }
    printf("%lf",l);
    return 0;
}

高精算法度

高精度加法

题目链接

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

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>0) C.push_back(t);
   return C;
}
int main()
{
    string a,b;
    cin>>a>>b;
    vector<int>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=(int)C.size()-1;i>=0;i--)
    {
        cout<<C[i];
    }
    return 0;
}

高精度减法

题目链接

#include<iostream>
#include<vector>
using namespace std;
//比较A,B的大小
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;
    for(int i=0,t=0;i<(int)A.size();i++)
    {
        t=A[i]-t;
        if(i<(int)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;
}
int main()
{
    string a,b;
    cin>>a>>b;
    vector<int>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(cmp(A,B)){//A>B
        auto C = sub(A,B);
        for(int i=C.size()-1;i>=0;i--)cout<<C[i];
    }else{//A<B
        auto C = sub(B,A);
        
        cout<<"-";
        for(int i=C.size()-1;i>=0;i--)cout<<C[i];
    }
    return 0;
}

高精度乘法

高精度x低精度

题目链接

#include<iostream>
#include<vector>
using namespace std;
vector<int>mul(vector<int>&A,int b)
{
    vector<int>C;
    int t=0;
    for(int i=0;i<(int)A.size();i++)
    {
        t=t+A[i]*b;
        C.push_back(t%10);
        t/=10;
    }
    //把剩余的数字放入数组
    while(t>0)
    {
        C.push_back(t%10);
        t/=10;
    }
    //去除前导0.如1213*0
    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');
    auto C=mul(A,b);
    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>div(vector<int>&A,int b,int &r)
{
    vector<int>C;
    for(int i=A.size()-1;i>=0;i--)
    {
        r=A[i]+r*10;
        C.push_back(r/b);
        r%=b;
    }
    reverse(C.begin(),C.end());//倒转,把顺序改为逆序,
    while(C.size()>1&&C.back()==0)C.pop_back();//去除前导0
    return C;
}

int main()
{
    string a;
    int b,r=0;
    cin>>a>>b;
    
    vector<int>A;
    for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');//逆序
    
    auto C=div(A,b,r);
    
    for(int i=C.size()-1;i>=0;i--)cout<<C[i];//逆序输出
    cout<<endl<<r<<endl;//余数
    return 0;
}

前缀和差分

前缀和和差分互逆,S[n]=a1+a2+...+a[n],那么S为a的前缀和,a为s的差分

一维前缀和

用于求任意区间的和

Sn=a[1]+...+a[n];

S(l-r)=S[r]=S[l-1];

题目链接

#include<iostream>
using namespace std;

const int N=100010;
int q[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&q[i]);
    }
    for(int i=1;i<=n;i++)q[i]=q[i-1]+q[i];
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",q[r]-q[l-1]);
    }
    return 0;
}

二维前缀和

左上角和右下角两点之间的矩形内的所有数和

题目链接

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

int main()
{
    int m,n,q;
    scanf("%d%d%d",&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++)
            a[i][j]=a[i-1][j]+a[i][j-1]-a[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",a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]);
    }
    return 0;
}

一维差分

题目链接

#include<iostream>
using namespace std;
const int N=100010;
//a原数组,b差分数组
int a[N],b[N];


//对差分数组进行插入操作,可将原数组[l,r]之间的没个数都+c
void insert(int l,int r,int c)
{
    //对差分数组l和r+1进行+c操作,相当于原数组,[l,r]区间都加上c
    b[l]+=c;
    b[r+1]-=c;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    //原数组
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    //求a数组的差分,相当于对前缀和和差分都为0的数组进行n次插入操作
    for(int i=1;i<=n;i++)insert(i,i,a[i]);
    //对b数组(a的差分数组)进行修改
    while(m--){
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        insert(l,r,c);
    }
    //对b数组求前缀和就是a被修改后的数组
    for(int i=1;i<=n;i++)b[i]=b[i-1]+b[i];
    for(int i=1;i<=n;i++)printf("%d ",b[i]);
    return 0;
}

二维差分

题目链接

#include<iostream>
using namespace std;

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

//修改差分数组,从而实现对原数组内某个矩形的所有值+c,
//最后只需对差分数组求前缀和就可得到修改后的数组
void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x1][y2+1]-=c;
    b[x2+1][y1]-=c;
    b[x2+1][y2+1]+=c;
}
int main()
{
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    //构造a的差分数组b
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            insert(i,j,i,j,a[i][j]);
    //对差分数组进行修改
    while(q--)
    {
        int x1,y1,x2,y2,c;
        cin>>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++)
            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++) printf("%d ",b[i][j]);
        cout<<endl;
    }       
    return 0;
}

双指针

最长连续不重复区间

题目链接

#include<iostream>
using namespace std;

const int N=100010;
int mp[N],a[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++)
    {
        //记入已经在i-j之间的数字
        mp[a[i]]++;
        //去重
        while(mp[a[i]]>1)
        {
            mp[a[j]]--;
            j++;
        }
        res=max(i-j+1,res);
        
    }
    cout<<res;
    return 0;
}

数组元素的目标和

题目链接

#include<iostream>
using namespace std;

const int N=100010;
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 j=0;j<m;j++)cin>>b[j];
    
    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;
            break;
        }
    }
    return 0;
}

 判断子序列

题目链接

#include<iostream>
using namespace std;

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

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)cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
}

位运算

n的二进制第k位数字  n>>k&1

获取第k位数字
#include<iostream>
using namespace std;
int main()
{
    int a = 10;
    //3210
	//1010   最后以为为0,以此类推
	// 101   //右移一位
	//&001   将个为前面的数字全部变为0,若最后一位为0就是0,是1就是1
	// 001=>1
	cout << (a >> 1 & 1) << endl;//1
}
 将一个数转化为二进制
#include<iostream>
using namespace std;
int main()
{
    int a = 10;
	int k = 0;
	int b = a;
	while (b) {
		b /= 2;
		k++;
	}
	k--;
	for(int k=3;k>=0;k--)
	cout << (a >> k & 1) ;
}

n的二进制中最后一位1  x&(~x+1)

将二进制中的最后一位1保留,其余位全部变为0

二进制中1的个数

题目链接

#include<iostream>
using namespace std;

int n;
//x&-x=>x&(~x+1)
//10=>1010=>0110=>0010
int lowbit(int x)
{
    return x & -x;
}

int main()
{
    cin>>n;
    while(n--)
    {
        int x;
        cin>>x;
        //如果最后一位1存在
        int res=0;
        while(x)
        {
            //  1010
            //- 0010
            //  1000    运算一次少一个1,减多少次就有几个1
            x-=lowbit(x);
            res++;
        }
        cout<<res<<" ";
    }
    return 0;
}

离散化

值域很大,但数字分布很稀疏

题目链接

区间合并

题目链接

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

typedef pair<int,int> PII;

vector<PII>segs;

void merge()
{
    //接收答案
    vector<PII>res;
    //左端点排序
    sort(segs.begin(),segs.end());
    //标记区间的左右端点
    int st=-2e9,ed=-2e9;
    for(auto seg:segs)
    {
        if(seg.first>ed)
        {
            //如果大于右端点,则是一段新区间,把前一段区间放入集合,if语句排除第一个空区间
            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;
}

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        segs.push_back({l,r});
    }
    merge();
    cout<<segs.size()<<endl;
    return 0;
}

数据结构

数组模拟链表,中途被删去的结点的空间不用考虑不再使用

单链表

题目链接

#include<iostream>
using namespace std;

const int N=100010;
/*
    head 头节点
    e[i]储存值
    ne[i]储存下一个节点下标
    idx 表示空结点,可以插入值
*/
int head,e[N],ne[N],idx;

//初始化
void init()
{
    head=-1;//初始化,头节点为-1
    idx=0;//空闲结点数组下标为0
}
//头插
void add_to_head(int x)
{
    e[idx]=x;//在新节点储存值
    ne[idx]=head;//新节点的下一个结点,改为头节点下一个结点
    head=idx;//头结点指向新节点
    idx++;。、//空结点后移
}
//在第k个数后面插入
void add(int k,int x)
{
    e[idx]=x;//在新节点储存值
    ne[idx]=ne[k];//新节点的下一个结点,改为第k个结点下一个结点
    ne[k]=idx;//第k个结点的下一个结点改为新节点
    idx++;//空结点后移
}
//在第k个数后面删除
void remove(int k)
{
    ne[k]=ne[ne[k]];//第k个结点直接指向下下个结点
}


int main()
{
    init();
    int n;
    cin>>n;
    while(n--)
    {
        int k,x;
        
        char op;
        cin>>op;
        
        if(op=='H')
        {
            cin>>x;
            add_to_head(x);
        }   
        else if(op=='D')
        {
             cin>>k;
             if(k==0)head=ne[head];//如果k=0,则删除第一个结点
             else remove(k-1);//第k个节点子数组中的下标为k-1,0为第一个结点
        }
        else
        {
            cin>>k>>x;
            add(k-1,x);//第k个节点子数组中的下标为k-1,0为第一个结点
        }
    }
    for(int i=head;i!=-1;i=ne[i]) cout<<e[i]<<" ";
    return 0;
}

双链表

题目链接

#include<iostream>
using namespace std;

const int N=100010;

int e[N],l[N],r[N],idx;
//初始化
void init()
{
    r[0]=1;//索引0为头节点
    l[1]=0;//索引1为末尾结点,所以下标为1时结束
    idx=2;//空结点从2开始
}
void remove(int k)
{
    r[l[k]]=r[k];//把k结点的前一个结点的右指针指向k节点的下一个结点
    l[r[k]]=l[k];//把k结点的右节点的左指针指向k结点的左结点
}

void add(int k,int x)
{
    e[idx]=x;//给空结点赋值
    l[idx]=k;//新节点的左指针指向k结点
    r[idx]=r[k];//新节点的右节点指向k结点的下一个结点
    l[r[k]]=idx;//k结点的下一个结点的左指针指向新节点
    r[k]=idx;//k结点的右指针指向新节点
    idx++;//指向下一个空结点
}
int main()
{
    init();
    int n;
    cin>>n;
    while(n--)
    {
        int k,x;
        string op;
        cin>>op;
        //第一个数从索引2开始,所以第k个数的下标索引为k+1
        if(op=="L"){//在最左端插入就是在头节点的右边插入
            cin>>x;
            add(0,x);
        }else if(op=="R"){//在最右边插入就是在末尾结点的左边结点后面插入
            cin>>x;
            add(l[1],x);
        }else if(op=="D"){//删除
            cin>>k;
            remove(k+1);
        }else if(op=="IL"){//在k结点的左边插入实际上实在k结点的上一个结点的右边插入
            cin>>k>>x;
            add(l[k+1],x);
        }else{//在k结点的右边插入
            cin>>k>>x;
            add(k+1,x);
        }
    }
    //打印数组,i=1结束
    for(int i=r[0];i!=1;i=r[i])cout<<e[i]<<" ";
    cout<<endl;
    return 0;
}

模拟栈

题目链接

#include<iostream>
using namespace std;

const int N=100010;
//栈数组,栈顶坐标
int stk[N],tt;

//入栈
void push(int x)
{
    stk[++tt]=x;
}
//出栈
void pop()
{
    if(tt>0)tt--;
}
//是否为空
bool isEmpty()
{
    return tt==0;
}
//返回栈顶元素
int query()
{
    return stk[tt];
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        string op;
        cin>>op;
        if(op=="push"){
            cin>>x;
            push(x);
        }else if(op=="pop"){
            pop();
        }else if(op=="query"){
            cout<<query()<<endl;
        }else{
            if(isEmpty())cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }
    }
    return 0;
}

表达式运算

题目链接

#include<iostream>
using namespace std;
#include<stack>
#include<algorithm>
#include<unordered_map>
#include<string>
stack<int>num;
stack<char>op;

unordered_map<char,int>h{{'+',1},{'-',1},{'*',2},{'/',2}};
//运算函数
void eval()
{
    int a=num.top();num.pop();//第二个操作数
    int b=num.top();num.pop();//第一个操作数
    char p=op.top();op.pop();//运算符
    int r=0;//运算结果
    if(p=='+')r=b+a;
    if(p=='-')r=b-a;
    if(p=='*')r=b*a;
    if(p=='/')r=b/a;
    num.push(r);//结果入栈
}

int main()
{
    string s;
    cin>>s;
    for(int i=0;i<s.size();i++)
    {
        if(isdigit(s[i]))//如果为数字,直接入栈
        {
            int x=0,j=i;
            //循环获得完整数字
            while(j<s.size()&& isdigit(s[j]))
            {
                x=x*10+s[j]-'0';
                j++;
            }
            num.push(x);//入栈
            i=j-1;//最后还得i++;所以i=j-1,i++之后重新变为j
        }else if(s[i]=='('){//如果为左括号直接入栈
            op.push(s[i]);
        }else if(s[i]==')'){//如果为右括号,把括号内的数字进行运算
            while(op.top()!='(')//循环运算,知道遇到左括号
            {
                eval();
            }
            op.pop();//左括号出栈
        }else{
            //如果新的运算符优先级低于栈内优先级,则将栈内运算符进行运算
            while(op.size()&&h[s[i]]<=h[op.top()])eval();
            op.push(s[i]);//将新的运算符入栈
        }
    }
    while(op.size())eval();//将栈内没有运算的的数进行运算
    cout<<num.top()<<endl;//输出结果
    return 0;
}

模拟队列

题目链接

#include<iostream>
using namespace std;

const int N=100010;
//队列数组,队首索引,队尾索引
int q[N],hh,tt=-1;

//入栈
void push(int x)
{
    q[++tt]=x;
}
//出栈
void pop()
{
    if(hh<=tt)hh++;
}
//是否为空
bool isEmpty()
{
    return hh>tt;//队首索引大于队尾索引则为空
}
//返回栈顶元素
int query()
{
    return q[hh];
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        string op;
        cin>>op;
        if(op=="push"){
            cin>>x;
            push(x);
        }else if(op=="pop"){
            pop();
        }else if(op=="query"){
            cout<<query()<<endl;
        }else{
            if(isEmpty())cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }
    }
    return 0;
}

单调栈

#include<iostream>
using namespace std;

const int N=100010;
//单调递增的栈
int stk[N],tt;

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        while(tt&&stk[tt]>=x)tt--;//如果栈顶元素大于新元素,直接pop
        if(tt)cout<<stk[tt]<<' ';//循环结束后,新元素前面要么没元素,要么就比新元素小,所以栈不为空,那么栈顶元素就是左边最近的更小元素
        else cout<<-1<<" ";//如如果为空则不存在比新元素小的数,输出-1
        stk[++tt]=x;//将新元素入栈
    }
    return 0;
}

滑动窗口(优先队列)

数组模拟队列

题目链接

#include<iostream>
using namespace std;  

const int N=1000010;
//优先队列--单调递增,队列保存下标
int q[N],hh,tt,a[N];

int n,k;

int main()
{
    cin>>n>>k;
    //给数组赋值
    for(int i=0;i<n;i++)cin>>a[i];
    //求最小值
    hh=0,tt=-1;//初始化队列
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;//如果队头不在窗口内,pop出队列
        while(hh<=tt&&a[q[tt]]>=a[i])tt--;//如果队尾元素大于新元素,pop出队列
        q[++tt]=i;
        if(i>=k-1)cout<<a[q[hh]]<<" ";
    }
    cout<<endl;
    //求最大值
    hh=0;tt=-1;//初始化队列
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;//如果队头不在窗口内,pop出队列
        while(hh<=tt&&a[q[tt]]<=a[i])tt--;//如果队尾元素大于新元素,pop出队列
        q[++tt]=i;
        if(i>=k-1)cout<<a[q[hh]]<<" ";
    }
    return 0;
}

kmp算法

题目链接

#include <iostream>

using namespace std;

const int N = 1000010;
char p[N], s[N]; // 用 p 来匹配 s
// “next” 数组,若第 i 位存储值为 k
// 说明 p[0...i] 内最长相等前后缀的前缀的最后一位下标为 k
// 即 p[0...k] == p[i-k...i]
int ne[N]; 
int n, m; // n 是模板串长度 m 是模式串长度

int main()
{
    cin >> n >> p >> m >> s;

    // p[0...0] 的区间内一定没有相等前后缀
    ne[0] = -1;

    // 构造模板串的 next 数组
    for (int i = 1, j = -1; i < n; i ++)
    {
        while (j != -1 && p[i] != p[j + 1])
        {
            // 若前后缀匹配不成功
            // 反复令 j 回退,直至到 -1 或是 s[i] == s[j + 1]
            j = ne[j];
        }
        if (p[i] == p[j + 1]) 
        {
            j ++; // 匹配成功时,最长相等前后缀变长,最长相等前后缀前缀的最后一位变大
        }
        ne[i] = j; // 令 ne[i] = j,以方便计算 next[i + 1]
    }

    // kmp start !
    for (int i = 0, j = -1; i < m; i ++)
    {
       while (j != -1 && s[i] != p[j + 1])
       {
           j = ne[j];
       }
       if (s[i] == p[j + 1])
       {
           j ++; // 匹配成功时,模板串指向下一位
       }
       if (j == n - 1) // 模板串匹配完成,第一个匹配字符下标为 0,故到 n - 1
       {
           // 匹配成功时,文本串结束位置减去模式串长度即为起始位置
           cout << i - j << ' ';

           // 模板串在模式串中出现的位置可能是重叠的
           // 需要让 j 回退到一定位置,再让 i 加 1 继续进行比较
           // 回退到 ne[j] 可以保证 j 最大,即已经成功匹配的部分最长
           j = ne[j]; 
       }
    }

   return 0;
}

Trie树

Trie树
Trie树中有个二维数组 son[N][26],表示当前结点的儿子,如果没有的话,可以等于++idx。Trie树本质上是一颗多叉树,对于字母而言最多有26个子结点。所以这个数组包含了两条信息。比如:son[1][0]=2表示1结点的一个值为a的子结点为结点2;如果son[1][0] = 0,则意味着没有值为a子结点。这里的son[N][26]相当于链表中的ne[N]

void insert(char str[])
{
    int p = 0; //从根结点开始遍历
    for (int i = 0; str[i]; i ++ )
    {
        int u =str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx; //没有该子结点就创建一个
        p = son[p][u]; //走到p的子结点
    }

    cnt[p] ++; // cnt相当于链表中的e[idx]
}

Trie字符串统计

题目链接

#include<iostream>
using namespace std;

const int N=100010;
//son记录下一个结点的值,cn,记录这个结点是否是某个字符串的结尾字符,值代表个数,idx代表下一个空结点
int son[N][26],cn[N],idx;//下标0既是根结点又是空结点
char str[N];
//插入
void insert(char str[])
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int u=str[i]-'a';//将字母转化为数字
        if(!son[p][u])son[p][u]=++idx;//若结点不存在,则创建结点
        p=son[p][u];//走到p的子节点
    }
    cn[p]++;//字符串遍历完成后,在末尾记录该字符串个数
}
//查找
int query(char str[])
{
    int p=0;//类似指针
    for(int i=0;str[i];i++)
    {
        int u=str[i]-'a';//将字母转化为数字
        if(!son[p][u])return 0;//如果字母存在,说明该字符串未记录
        p=son[p][u];//p指向儿子结点
    }
    return cn[p];//返回字符串个数
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        char op[2];
        scanf("%s%s",op,str);//输入字符串
        if(op[0]=='I')insert(str);//获取第一个字符,判断是什么操作
        else cout<<query(str)<<endl;//输出结果
    }
    return 0;
}

最大异或对

题目链接

#include<iostream>
using namespace std;

const int N=100010,M=31*N;
int n,a[N];
int son[M][2],idx;
//插入
void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--)//总共31位,0-30
    {
        int u=x>>i&1;//获取第i位数字
        if(!son[p][u])son[p][u]=++idx;//如果结点不存在,开辟新节点
        p=son[p][u];//指向儿子结点
    }
}
int search(int x)
{
    int p=0,res=0;//res记录大小
    for(int i=30;i>=0;i--)//总共31位,0-30
    {
        int u=x>>i&1;//获取第i位数字
        if(son[p][!u])//若不同数字存在,则该位结果为1,比如二进制位的数字为1,若存在为0的数字,则最大结果为1
        {
            p=son[p][!u];//进入另一个分支,遇到不同数字概率更大
            res=res*2+1;//该位结果位1
        }else{//若不存在不同的数字,则结果为0
            p=son[p][u];//只能进入本分支
            res=res*2+0;//该位结果为0
        }
    }
    return res;//返回最大结果
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        insert(a[i]);//插入
    }
    int res=0;
    for(int i=0;i<n;i++)
        res=max(res,search(a[i]));//查询
    cout<<res<<endl;//输出结果
    return 0;
}

并查集

合并集合

题目链接

#include<iostream>
using namespace std;

const int N=100010;
int n,m,p[N];
//查找根节点
int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);//如果不是根结点,则一直向上寻找,并将所有途经结点指向根节点,使下一次查找变为O(1)
    return p[x];//返回根节点的值
}

int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)p[i]=i;//一开始每个元素都是一个集合,所以都是根节点
    while(m--)
    {
        char op;int a,b;
        cin>>op>>a>>b;
        if(op=='M')p[find(a)]=find(b);
        else{
            if(find(a)==find(b))cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

连通块中点的数量

题目链接

#include<iostream>
using namespace std;

const int N=100010;
int p[N],Size[N],n,m;
//查找根节点
int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
        Size[i]=1;
    }
    while(m--)
    {
        char op[5];int a,b;
        scanf("%s",op);
        if(op[0]=='C')//合并集合
        {
            cin>>a>>b;
            int x=find(a),y=find(b);//查找a,b的根节点
            if(x==y)continue;//如果在同一个集合,不用合并
            Size[x]+=Size[y];
            p[y]=p[x];
        }else if(op[1]=='1'){//判断a,b是否在一个集合
            cin>>a>>b;
            if(find(a)==find(b))puts("Yes");
            else puts("No");
        }else{//输出a所在集合大小
            cin>>a;
            cout<<Size[find(a)]<<endl;
        }
    }
    return 0;
}

食物链

题目链接

#include<iostream>
using namespace std;

const int N=50010;
int p[N],d[N];
int n,k;

int find(int x)
{
    if(p[x]!=x)
    {
        int t=find(p[x]);//储存根节点
        d[x]+=d[p[x]];//到上一个节点的距离+上一个结点到根节点的距离=本结点到根节点的距离
        p[x]=t;//指向根节点;
    }
    return p[x];
}

int main()
{
    cin>>n>>k;
    int res=0;
    for(int i=1;i<=n;i++)p[i]=i;
    while(k--)
    {
        int t,x,y;
        cin>>t>>x>>y;
        if(x>n||y>n)res++;
        else {
            int py=find(y),px=find(x);
            if(t==1)
            {
                if(px==py&&(d[x]-d[y])%3)res++;//如果二者在同一个集合但到根节点的距离不相等,所以不是同类
                else if(px!=py){//如果不在一个集合,和前面的为真话不冲突,所以为真,按所给关系,合并集合
                    p[px]=p[y];//x的根节点指向y的根节点
                    d[px]=d[y]-d[x];//因为x,y同类,所以dx+d[px]-dy=0,则d[px]=dy-dx
                }
            }else{
                if(px==py&&(d[x]-d[y]-1)%3)res++;//如果二者在同一个集合但到根节点的距离差不相等1,所以不是同类
                else if(px!=py)
                {
                    p[px]=py;//x的根节点指向y的根节点
                    d[px]=d[y]+1-d[x];//因为x吃y,所以dx+d[px]-dy-1=0,则d[px]=dy+1-dx
                }
            }
        }
    }
    cout<<res<<endl;
}

堆排序

堆排序

题目链接

#include<iostream>
using namespace std;

const int N=100010;
int n,m;
int h[N],Size;

void down(int u)
{
    int t=u;//记录根的值
    if(u*2<=Size&&h[u*2]<h[t])t=u*2;//如果左儿子存在,并且小于根节点,则进行标记
    if(u*2+1<=Size&&h[u*2+1]<h[t])t=u*2+1;//如果右儿子存在,则与标记结点进行比较,若右节点更小,则进行标记
    if(u!=t)//如果被标记的的点(最小的儿子结点)不是根节点,说明存在一个值比根节点小,两个值进行交换
    {
        swap(h[u],h[t]);
        down(t);//将下移的值与他的儿子结点继续比较,直至符合小顶堆的规则
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>h[i];
    Size=n;
    for(int i=n/2;i;i--)down(i);//将数组维护成一个小顶堆
    while(m--)
    {
        cout<<h[1]<<' ';//输出堆顶的值
        h[1]=h[Size];//将最后一个值赋给堆顶
        Size--;//大小-1
        down(1);//向下移动,直至符合规则
    }
    return 0;
}

模拟堆

考试不需要,直接用优先队列,了解就行

题目链接

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 100010;

int h[N], ph[N], hp[N], cnt;

void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }

    return 0;
}

哈希表

可以不掌握,直接用set集合

 模拟散列表

题目链接

#include <cstring>
#include <iostream>

using namespace std;

const int N = 200003, null = 0x3f3f3f3f;

int h[N];

int find(int x)
{
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x)
    {
        t ++ ;
        if (t == N) t = 0;
    }
    return t;
}

int main()
{
    memset(h, 0x3f, sizeof h);

    int n;
    scanf("%d", &n);

    while (n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'I') h[find(x)] = x;
        else
        {
            if (h[find(x)] == null) puts("No");
            else puts("Yes");
        }
    }

    return 0;
}
拉链法
#include <cstring>
#include <iostream>

using namespace std;

const int N = 100003;

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

void insert(int x)
{
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++ ;
}

bool find(int x)
{
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i])
        if (e[i] == x)
            return true;

    return false;
}

int main()
{
    int n;
    scanf("%d", &n);

    memset(h, -1, sizeof h);

    while (n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);

        if (*op == 'I') insert(x);
        else
        {
            if (find(x)) puts("Yes");
            else puts("No");
        }
    }

    return 0;
}

字符串哈希

题目链接

#include <iostream>
#include <algorithm>

using namespace std;

typedef unsigned long long ULL;

const int N = 100010, P = 131;

int n, m;
char str[N];
ULL h[N], p[N];

ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    scanf("%d%d", &n, &m);
    scanf("%s", str + 1);

    p[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        h[i] = h[i - 1] * P + str[i];
        p[i] = p[i - 1] * P;
    }

    while (m -- )
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);

        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }

    return 0;
}

搜索和图论

dfs

排列数字

题目链接

#include<iostream>
using namespace std;

const int N=10;
int path[N],n;
bool st[N];//记录数字是否被用过
void back(int u)//u记录path数组的下表
{
    if(u==n)//所有数字用完
    {
        for(int i=0;i<n;i++)
        {
            cout<<path[i]<<" ";
        }
        cout<<endl;
    }
    for(int i=1;i<=n;i++)
    {
        if(!st[i])//如果数字么被用过
        {
            path[u]=i;
            st[i]=true;
            back(u+1);
            st[i]=false;
        }
    }
}
int main()
{
    cin>>n;
    back(0);
    return 0;
}

动态规划

背包问题

01背包

题目链接

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int w[N],v[N];
int n,m;
int dp[N][N];
/*
dp数组的含义:dp[i][j]
              在物品数为i,容积为j时,所装物品的最大价值
*/
int main()
{
    
    
    //二维
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(j<v[i])dp[i][j]=dp[i-1][j];
            else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
        }
    cout<<dp[n][m];
    
    
    
    //一维
    // cin>>n>>m;
    // for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    // for(int i=1;i<=n;i++)
    //     for(int j=m;j>=v[i];j--)
    //         f[j]=max(f[j],f[j-v[i]]+w[i]);
    // cout<<f[m];
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值