算法基础的基础模板

一.排序

(1)快速排序

const int N = 1e6 + 10;
int a[N], n;//从0开始
void quick_sort(int l, int r)
{
	int i = l, j = r;//二分思想
	int mid = a[(l + r) / 2];
	do{
		while(a[i] < mid)//严格小于,i指针才往右移动
			++ i;
		while(a[j] > mid)//严格大于,j指针才往左移动
			-- j;
		if(i <= j){//双指针都要记得判断边界
			swap(a[i], a[j]);//交换后i,j继续移动
			++ i;
			-- j;
		}
	}while(i <= j);//如果i<=j则继续移动,结束后会被分为三个区间,[l, j], x, [i, r]
	if(j > l)//当i>j时才会退出上述循环进行下一步的操作
		quick_sort(l, j);//分治,递归地排序
	if(i < r)
		quick_sort(i, r);
}
quick_sort(0, n - 1);

快排应用题

(2)归并排序

void merge_sort(int a[],int left,int right)//本来想用vector的,结果gg,QAQ  
{
    if(left>=right)//递归终止条件
        return;
    int mid=(left+right)>>1;
    merge_sort(a,left,mid);//左右区间分别进行递归,最后得到的两个区间都各自有序
    merge_sort(a,mid+1,right);
    int i=left,j=mid+1;//双指针,分别从两个区间的起点开始
    int cnt=0;
    while(i<=mid && j<=right){  //合并两个区间
        if(a[i]<=a[j])
            temp[cnt++]=a[i++];
        else
            temp[cnt++]=a[j++];
    }
    while(i<=mid)
        temp[cnt++]=a[i++];
    while(j<=right)
        temp[cnt++]=a[j++];
    for(int i=left,j=0;i<=right;++i,++j)   //关键步骤,把某个递归后的结果返还回去,不注意这里会很惨
        a[i]=temp[j];
}
merge_sort(a,0,n-1);	//n个数

二.二分法

1.整数二分:

整数二分有两种情况,一种是目标数在右区间,另一种是目标数在左区间,因此有两种板子;(k即为target,l为左边界left,r为右边界right)
重点看第3,4部分题目的check函数下面部分的边界情况考虑,稍微想想就行,思考哪些是可取的,哪些是不可取的,还有右边的怎么样,左边的怎么样,如当前值可取,右边值也可取,则left=mid,当前值不可取,右边值也不可取,则right=mid-1,当前值不可取,右边值可取,则left=mid+1,这样根据问题思考后再套模板即可轻松求解二分(因为[l,r]中都是可能取值,包括l,r,一旦离开该区间,则说明为不可行解);

(1)第一种板子
int search_1(int l,int r)
{  //二分时首先确定目标是在左区间还是在右区间,要提前想好,关键
	while(l<r){
        int mid=(l+r)>>1;//由下面的推出mid,若r=mid,则mid=(l+r)>>1
        if(a[mid]>=k)//关键,目标为大于等于某个数,在右区间,所以直接根据题目要求使得a[mid]>=k即可
            r=mid;      //因为是求大于等于某个数中的最小值,所以直接缩小范围,只要求最小的即可,在mid右边的肯定大于等于mid,所以mid就够了
        else
            l=mid+1;
    }
}
(2)第二种板子
int search_2(int l,int r)
{
	while(l<r){
	    int mid=(l+r+1)>>1;//由下面的推出mid,若l=mid,则mid=(l+r+1)>>1
	    if(a[mid]<=k)//关键,目标为小于等于某个数,在左区间,所以直接根据题目要求使得a[mid]<=k即可
	        l=mid;  //因为是求小于等于某个数中的最大值,所以直接缩小范围,只要求最大的即可,在mid左边的肯定都小于等于mid,所以mid在[,mid]中是最大的,故取mid
	    else
	        r=mid-1;
	}
}

若查找失败,则

if(a[l]!=k)
	printf("无目标数\n");

模板题

2.浮点数二分:

浮点数二分比整数二分简单,因为不需要考虑边界;

bool check(double x) 
{/* ... */} // 检查x是否满足某种性质
double search_3(double l, double r)
{
    const double eps = 1e-6;   // 保留四位小数为1e-6,保留五位小数为1e-7,保留六位小数为1e-8
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;	//浮点数二分不考虑边界,所以l=mid即可
    }
    return l;
}

3.跳石头(非常好的一道理解二分的题目!)

题目链接
二分答案,挺有意思的一道题;
正常去想是不行的,要反其道而行之,去枚举答案距离,再用该距离去计算需要移除的石头数,如果石头数超过题目所给,说明该距离太大了,要缩小该距离,如果石头数符合,由于是求max(最短跳跃距离),所以可以先记录一下当前距离,然后放大距离,看是不是也符合要求,这样就可以去用二分枚举答案距离;
如何计算需要移除的石头数:计算当前所在石头与下一块石头的距离,如果这个距离小了,又由于所假设的距离就是答案距离,所以该石头需要被移除;

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e4 + 10;
int a[N];
int len, n, m;
bool check(int k)//前提假设k就是正确答案,即k为max(最短跳跃距离),小于k的跳跃距离涉及的石头都要被拿掉
{
    int cnt = 0;
    int s = 0;//当前石头与起点的距离
    for(int i = 1; i <= n + 1; ++ i){
        if(a[i] - s >= k)//a[i]为当前石头的下一块石头与起点的位置,两者之差即为跳跃距离(两石头距离)
            s = a[i];//如果大于等于k,则说明当前跳跃距离不小于当前最短跳跃距离,不移走该石头,更新当前所在石头与起点距离
        else
            cnt ++;//如果小于k,则不符合前提假设,拿掉该石头
    }
    if(cnt > m)//统计拿掉的石头,如果比题目要求的还多,说明此k为不可行解
        return false;
    else
        return true;
}
int main()
{
    cin >> len >> n >> m;
    for(int i = 1; i <= n; ++ i)
        scanf("%d", &a[i]);
    a[n + 1] = len;
    int l = 1, r = len;
    while(l < r){
        int mid = (l + r + 1) >> 1;//要理解[l,r]中的l,r都是可能的取值,由r=mid-1推出该式要+1
        if(check(mid)){//因为mid是可行解,说明mid后面可能有更好的解,因为是求的max(最短跳跃距离),解在右边
            l = mid;//可行就保留,即l=mid
        }
        else//如果mid不可行,说明跳跃距离太大了,太多石头被拿了,如果mid更大,那么将有更多的石头被拿,所以要缩小mid的值
            r = mid - 1;//不可行就移除,即r=mid-1,这样mid-1就再也不会被考虑了
    }
    cout << l << endl;
    return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.砍树(也是一道帮助理解二分的题目)

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
ll a[N];
int main()
{
    ll n, m;
    ll maxn = 0;
    cin >> n >> m;
    for(int i = 0; i < n; ++ i) {
        scanf("%lld", &a[i]);
        maxn = max(maxn, a[i]);
    }
    ll l = 1, r = maxn;
    ll mid;
    while(l <  r){
        ll res = 0;
        mid = (l + r + 1) >> 1;
        for(int i = 0; i < n; ++ i)
            if(a[i] > mid)
                res += a[i] - mid;
        if(res < m)//考虑边界情况,如果res小了,说明这个mid不可取,那么右边的mid更加不可取,所以r=mid-1
            r = mid - 1;
        else
            l = mid;//如果是大于等于,说明这个mid可取,也许后面的也行,所以是l=mid
    }
    cout << l << endl;
    return 0;
}

三.大数高精度处理

大数基本处理:

string a;
cin>>a;
vector<int> x;
for(int i=a.size()-1;i>=0;--i)//倒着存,即个位在首位,高位在后面,为了方便进位的处理,如果最高位在首位那么进位处理就比较麻烦了,所以最高位在vector的最右边
    x.push_back(a[i]-'0');
auto c=func(x,b);
for(int i=c.size()-1;i>=0;--i)
    printf("%d",c[i]);

1.大数加法:

vector<int> add(vector<int>& x,vector<int>& y)  //大数高精度加法
{
    vector<int> ans;
    int t=0;
    for(int i=0;i<x.size() || i<y.size();++i){  //t=x[i]+y[i]+ci,进位ci即为之前的t
        if(i<x.size())
            t+=x[i];
        if(i<y.size())
            t+=y[i];
        ans.push_back(t%10);
        t/=10;  //中间过程的进位
    }
    if(t)   //若最高位有进位则加上
        ans.push_back(t);
    return ans;
}

模板题

2.大数减法

vector<int> sub(vector<int>& x,vector<int>& y)
{
    vector<int> ans;
    int t=0;
    for(int i=0;i<x.size();++i){    //ans[i]=x[i]-y[i]-ci
        t=x[i]-t;   //减去进位
        if(i<y.size())
            t-=y[i];
        ans.push_back((t+10)%10);   //包含了两种情况,一是t>0,加10模10后仍为t本身,二是t<0,借位后模10
        if(t<0) //更新进位
            t=1;
        else
            t=0;
    }
    while(ans.size()>1 && ans.back()==0)    //去除前导0
        ans.pop_back();
    return ans;
}

减法还需考虑x-y<0的情况

bool cmp(vector<int>& x,vector<int>& y) //比较两个数的大小,若x>=y则返回true,否则返回false
{
    if(x.size() != y.size())    //先比较位数
        return x.size() > y.size();
    for(int i=x.size()-1;i>=0;--i){     //在位数相同的情况下依次从高位向低位进行比较
        if(x[i]!=y[i])
            return x[i]>y[i];
    }
    return true;
}
if(cmp(x,y)){   //保证传进sub函数的x>y
        auto c=sub(x,y);
        for(int i=c.size()-1;i>=0;--i)
            printf("%d",c[i]);
        cout<<endl;
    }
    else{   //若x<y,则x-y=-(y-x)
        cout<<'-';
        auto c=sub(y,x);
        for(int i=c.size()-1;i>=0;--i)
            printf("%d",c[i]);
        cout<<endl;
    }

模板题

3.大数乘法

(1)高精度*数
vector<int> mul(vector<int> & a, int b)
{
    vector<int> ans;
    int t = 0;
    for(int i = 0; i < a.size(); ++ i){
        t += a[i] * b;//ans[i] = a[i]*b+ci,将b看成是一个整体,使a的每一位与之相乘
        ans.push_back(t % 10);//保留尾数
        t /= 10;//进位
    }
    while(t){//和高精度加法一样,需要考虑最高位的进位
        ans.push_back(t % 10);
        t /= 10;
    }
    while(ans.size() > 1 && ans.back() == 0)//去除多余的前导0
        ans.pop_back();
    return ans;
}
(2)高精度*高精度
vector<int> mul(vector<int> & a, vector<int> & b)
{
    vector<int> ans(a.size() + b.size(), 0);//将可能用到的部分都初始化成0,如999*99,3位数乘以2位数最多不会超过5位数
    for(int i = 0; i < a.size(); ++ i)//不初始化的话会RE
        for(int j = 0; j < b.size(); ++ j)//类似预处理了
            ans[i + j] += a[i] * b[j];//手动模拟一下就挺好理解的,如65*34
    int t = 0;
    for(int i = 0; i < ans.size(); ++ i){//类似其他高精度
        t += ans[i];
        ans[i] = t % 10;
        t /= 10;
    }
    while(ans.size() > 1 && ans.back() == 0)//去除前导0
        ans.pop_back();
    return ans;
}

模板题

4.大数除法

vector<int> div(vector<int>& a,int b,int& r)    //传入r的引用,使得r在函数中变化后,也能在main函数中得到变化后的r
{
    vector<int> ans;
    r=0;
    for(int i=a.size()-1;i>=0;--i){     //也是把b看成一个整体
        r=r*10+a[i];    //ans[i]=(r*10+a[i])/10
        ans.push_back(r/b);
        r%=b;
    }
    reverse(ans.begin(),ans.end());     //因为此时正序即为商,但为了和外部接口保持一致,需要翻转一下
    while(ans.size()>1 && ans.back()==0)    //去除多余的前导0
        ans.pop_back();
    return ans;
}

模板题
总结:大数运算主要有以下几个步骤:
1.对输入的大数进行处理后倒序放入vector中;
2.在函数内部先令进位t或者余数r=0,然后根据加减乘除各个运算的特点,按位依次运算;
3.减乘除还需注意去除多余的前导0;
4.倒序输出vector;

四、前缀和及差分

1.前缀和

xtuOJ经常要用的一种思想(谢大出的题真的有点。。。)
可以把原本O(n)的查询时间降为O(1);
需要设置f[0]=0,f[0][0]=0等初始条件处理边界值,这样就不需要特判了。

(1)一维情况及最大子段和

给出区间[l,r],求其上的a[l]+a[l+1]+···+a[r],可转化为f[r]-f[l-1];

公式:
f[i]=f[i-1]+a[i]
f[0]=0

模板题
前缀和一般从1开始

最大子段和:给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大
2 -4 3 -1 2 -4 3
3 -1 2为最大子段和对应子段
状态转移方程: f [ i ] = m a x ( a [ i ] , a [ i ] + f [ i − 1 ] ) f[i] = max(a[i],a[i]+f[i-1]) f[i]=max(a[i],a[i]+f[i1]) f [ i ] f[i] f[i]为到 i i i为止的的最大子段和)

(2)二维情况和二维最大子段和

子矩阵的和

基本公式:
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]
在这里插入图片描述

模板题
二维最大子段和(可求矩阵中任意大小的子矩阵,如求最大子矩阵之和)
题目链接
题解

重点:压缩矩阵
在使用动态规划求解序列(矩阵)连续最大和的过程中,答案ans记录的是从开始到当前位置的最大和,所以在遍历的时候ans永远是到当前位置为止已知的最大值。
二维矩阵求解的过程,要分三个部分进行求解,即i,k,j分别表示当前最下面的行(最下面的行是第i行),压缩行(即连续的k行(k=1,…,i),从压缩1行到压缩i行),和列(当行和压缩行确定后,就相当于是求一维的子段最大和了)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =130;
int a[N][N];
int ans;
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j) //重点是把二维的压缩成一维的进行处理
            scanf("%d", &a[i][j]);
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j) 
            a[i][j] += a[i - 1][j];//压缩矩阵,就不用遍历行了,a[i][j]-a[i-k][j],i=5,k=3,就是压3行,
    for(int i = 1; i <= n; ++ i){//即把第1,2行减去,剩下的就是3,4,5行,求3~5行的最大子矩阵,再遍历j(列)即可
        for(int k = 1; k <= i; ++ k){//把以i为最下面一行的矩阵压k行!!!
            int f[N] = {0};//这里就是类似一维最大子段和
            for(int j = 1; j <= n; ++ j){//枚举每一列,就类似一维最大子段和从前往后遍历
                f[j] = a[i][j] - a[i - k][j];//此时f[j]就是一维最大子段和中的每一个元素
                f[j] = max(f[j], f[j] + f[j - 1]);//res[j]就是到j为止的的最大子段和
                ans = max(ans, res[j]);//更新最大子矩阵答案
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

2.差分

(1)一维差分

差分可以看做是前缀和的逆运算。
如已知f[i],需要求a[i],使得a[0]+a[1]+···+a[i]=f[i],a[i]就被称为f[i]的差分。
效果也是使得O(n)的操作变为O(1);

一种简单的构造方式:
a[0]=f[0]
a[1]=f[1]-f[0]
a[2]=f[2]-f[1]
···
a[i]=f[i]-f[i-1]
这样就可以保证a[0]+a[1]+···+a[i]=f[i]

应用
在这里插入图片描述
在这里插入图片描述
模板题

#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
    int n,m;
    int l,r,c;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)   //从1到n的闭区间,为了使之后的差分构造保持一致
        scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)   //构造差分数组
        b[i]=a[i]-a[i-1];
    while(m--){
        scanf("%d%d%d",&l,&r,&c);
        b[l]+=c;    //关键步骤,若b[l]加上c,则后面求前缀和时,f[l]及后面的元素都加上了c,即实现了要求
        b[r+1]-=c;  //b[r+1]减去c,使得b[r]后面的元素在求前缀和时不会加上c,即实现了只有[l,r]内的元素加上c
    }
    for(int i=1;i<=n;++i)   //求前缀和,即返回答案数组
        b[i]+=b[i-1];	//由a[i]=a[i-1]+b[i],把a改为b即可
    for(int i=1;i<=n;++i)
        cout<<b[i]<<' ';
    cout<<endl;
    return 0;
}

(2)二维差分

模板题

#include<iostream>
using namespace std;
const int N=1e3+10;
int a[N][N];
int b[N][N];
int main()
{
    int n,m,q;
    int x1,y1,x2,y2,c;
    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)
            b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];//由s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]反推出a[i][j]即差分
    while(q--){
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
        b[x1][y1]+=c;   //(x1,y1)及其右下角的的数都加上c
        b[x2+1][y1]-=c;     //(x2,y1)下面的数都减去c,防止无关数受影响
        b[x1][y2+1]-=c;     //(x1,y2)右边的数都减去c,与上同理
        b[x2+1][y2+1]+=c;   //由于上两式,(x2,y2)右下角的数减了两次c,所以需要再加上一个c
    }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)//类比差分b[i]=f[i]-f[i-1],f[i]=b[i]+f[i-1],b[i]+=b[i-1]后,b[i]即为前缀和
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];//则由b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]
    for(int i=1;i<=n;++i) {//前缀和:a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+b[i][j], 把a改为b即可,  b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1]即为前缀和
        for (int j = 1; j <= m; ++j)
            printf("%d ", b[i][j]);
        cout<<endl;
    }
    return 0;
}

五、双指针

双指针最大的作用就是优化。如双重for循环,暴力的话需要O(n^2),但是用双指针的话只要 2 ∗ n 2*n 2n,即O(n),显著降低了时间复杂度。双指针也是如快排,归并等算法实现的关键。
做题时可以先写出O(n^2)的暴力写法,然后找出i,j间的关系以及单调性,再进行优化,变成 2 ∗ n 2*n 2n即O(n)的双指针算法。
经典例题:

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int a[N];
int s[N];
int main()
{
    int n;
    int ans=0;
    cin>>n;
    for(int i=0;i<n;++i)
        scanf("%d",&a[i]);
    for(int i=0,j=0;i<n;++i){   //i,j只能向右移动,当[j,i]为当前最大不重复区间时,由于i只能向右移动,此时[j-1,i+1]一定不会为不重复区间,[j,i+1]可能为重复区间,因为一旦[j-1,i+1]为不重复区间,则[j-1,i]也为不重复区间,与[j,i]为最大重复区间矛盾,即j不能左移,只能右移
        s[a[i]]++;  //记录a[i]出现的次数
        while(s[a[i]]>1){   //关键步骤,当出现重复元素时,维护区间[i,j]
            --s[a[j]];  //更新,将重复元素之前的值全部清零,使得只有当前元素(重复)(新区间起点)的出现次数为1,并且协助j的移动
            ++j;    //向右移动j,最后达到新区间的起点
        }
        ans=max(ans,i-j+1); //i到j的长度为i-j+1,更新
    }
    cout<<ans<<endl;
    return 0;
}

1.第一类用法

对于一个序列,用两个指针维护一段区间;
例题:去除多余空格

输入一个字符串,字符串中可能包含多个连续的空格,请将多余的空格去掉,只留下一个空格。
输入格式
共一行,包含一个字符串。
输出格式
输出去掉多余空格后的字符串,占一行。
数据范围
输入字符串的长度不超过200。
输入样例:
Hello world.This is c language.
输出样例:
Hello world.This is c language.

#include <iostream>
using namespace std;
int main()
{
    string str, res;
    getline(cin, str);
    for(int i = 0;i < str.size(); ++ i){
        if(str[i] == ' '){
            int j = i + 1;
            while(j < str.size() && str[j] == ' ') ++ j;
            i = j - 1;
        }
        res.push_back(str[i]);
    }
    for(int i = 0;i < res.size(); ++ i) printf("%c", res[i]);
    return 0;
}

例题链接

输入一个整数数组,实现一个函数来调整该数组中数字的顺序。
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
样例
输入:[1,2,3,4,5]
输出: [1,3,5,2,4]

class Solution {
public:
    void reOrderArray(vector<int> &array) {
         int i=0,j=array.size()-1;
         while(i<j){
            while(i<j && array[i]%2==1)
                ++i;
            while(i<j && array[j]%2==0)
                --j;
            if(i<j)
                swap(array[i],array[j]);
         }
    }
};

2.第二类用法

对于两个序列,维护某种次序;
如归并排序中的区间合并;

六、位运算

lowbit算法:

a&(-a)=最右的1以及后面的0
如3&(-3)=1 (3二进制为11,最右边1以及后面的数就是1)
10&(-10)=10 (10二进制为1010,最右边的1以及后面的数为10)

利用lowbit算法可以求解一个数中转化成二进制后1的个数。
模板题

#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;++i) {
        scanf("%d",&a[i]);
        int num=0;
        while(a[i]){
            if(a[i]&(-a[i])){
                num++;
                a[i]-=a[i]&(-a[i]);	//一步步去除有1的部分
            }
        }
        cout<<num<<" ";
    }
    cout<<endl;
    return 0;
}

七、离散化

离散化是一种非常特殊的hash方式,保序;

当题目给出的数据值范围量很大,但是实际上用到的数据数量不多时使用,将规模比较大的数转换成规模比较小的数;
基本思想:把所有要用到的数先全部放到一个数组(vector)中,而不需要考虑其数值范围,然后进行排序,去重,再通过一个find(二分查找)函数对其进行映射,将原本可能很大的数的存在范围映射成很小的范围,如1,1000,2000000变成1,2,3,即用下标(连续)来表示原来的值(要和hash区分开,hash不能排序);
> 重点:
1.读入所有数据到数组中;
2.排序,去重后得到有序不重复的基本数组x;
3.find通过二分查找将数据值根据其在x中的位置映射成从0开始或从1开始的下标;
4.最后进行对应的计算操作;

基本模板

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

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
	//return lower_bound(alls.begin(),alls.end,x) - all.begin + 1;	//或者直接用lower_bound二分函数返回数组下标
    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
}

模板题区间和:

假定有一个无限长的数轴,数轴上每个坐标上的数都是0。 现在,我们首先进行 n 次操作,每次操作将某一位置x上的数加c。 接下来,进行 m
次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。 输入格式 第一行包含两个整数n和m。 接下来 n
行,每行包含两个整数x和c。 再接下里 m 行,每行包含两个整数l和r。 输出格式 共m行,每行输出一个询问中所求的区间内数字和。 数据范围
−109≤x≤109, 1≤n,m≤105, −109≤l≤r≤109, −10000≤c≤10000 输入样例: 3 3 1 2 3 6
7 5 1 3 4 6 7 8 输出样例: 8 0 5

思路:易知需要用到前缀和,但由于数值范围是1e9,数组最多容量为1e8,且用到的数据数量只有3e5,所以要用离散化缩小数值范围:把需要用到的三种坐标:x,l,r放入vector数组中进行离散化排序去重得到有序无重复vector数组pos,然后再借助二分查找find函数将读入的要做加法操作以及查询操作的坐标根据其在pos中的位置映射到1~n(前缀和下标从1开始),即做离散化操作,然后再进行对应的加法或求前缀和;

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N=3e5+10;     //由于加法操作给出的坐标最多是1e5个,查询操作给出的坐标最多是2e5个,所以最大为3e5个
typedef pair<int,int> pii;      //二元组,存储<x,c>,<l,r>,即类似结构体,将两个变量关联起来
int a[N];   //区间点数组
int f[N];   //前缀和数组,f[i] = f[i-1] + a[i]
vector<pii> add,query;  //加法操作和查询操作都涉及两个有关联的变量
vector<int> pos;    //记录需要用到的位置坐标的数组,x,l,c都会被放入该数组
//为了方便使用前缀和,数组下标都从1开始
int find(int x)     //关键函数,通过其实现离散化
{
    //return lower_bound(pos.begin(),pos.end(),x)-pos.begin() + 1;    //lower_bound二分查找,返回对应元素下标
    int l = 0,r = pos.size() - 1;
    while(l < r){
        int mid = (l + r) >> 1;
        if(pos[mid] >= x)
            r = mid;
        else
            l = mid + 1;
    }
    return r+1;
}
vector<int>::iterator uni(vector<int>& a)   //手写unique去重函数
{
    int j = 0;
    for(int i = 0; i < a.size(); ++i){
        while(!i || a[i] != a[i-1])     //不会重复的条件有两个:第一个元素不会重复;排序后与前一个元素不同的元素不会重复
            a[j++] = a[i];
    }
    return a.begin() + j;   //返回未排好序的部分的第一个位置
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n,m;
    int x,c;
    int l,r;
    cin >> n >> m;
    while(n--){
        cin >> x >> c;
        add.push_back({x,c});   //放入二元组vector中,方便之后对位置x进行加法操作
        pos.push_back(x);   //放入坐标位置
    }
    while(m--){
        cin >> l >> r;
        query.push_back({l,r});     //也是放入二元组vector中,方便最后进行查询操作
        pos.push_back(l);   //放入坐标位置
        pos.push_back(r);
    }
    sort(pos.begin(),pos.end());    //先对存储坐标的数组进行排序
    pos.erase(unique(pos.begin(),pos.end()),pos.end());     //然后去重
    for(auto i : add){      //进行加法操作
        int y = find(i.first);  //取出坐标位置,并离散化,之后再做加法
        a[y] += i.second;   //加法
    }
    for(int i = 1; i <= pos.size(); ++i)    //求前缀和
        f[i] = f[i-1] + a[i];
    for(auto i : query){    //进行查询操作
        l = find(i.first);  //取出区间坐标后,离散化,再求对应的前缀和
        r = find(i.second);
        cout << f[r] - f[l - 1] << endl;
    }
}

模板题链接

八、区间合并

1、将每个区间按左端点从小到大进行排序;
2、排好序后的区间间有三种关系,且未合并好的区间都在在当前处理区间的的右侧,如图所示,可分3种情况:

1.下一区间与当前区间不相交;
2.下一区间与当前区间部分相交;
3.下一区间在当前区间内部
在这里插入图片描述

模板题链接

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> pii;
vector<pii> segs;   //定义二元组,包括区间起点和终点
void merge(vector<pii> &segs)
{
    vector<pii> temp;   //设置合并后的区间组
    sort(segs.begin(),segs.end());  //按照区间起点大小进行排序
    int start=-2e9,end=-2e9;    //设置区间,为inf
    for(auto seg : segs){   //排好序后的区间间有三种关系,且未合并好的区间都在在当前处理区间的的右侧:1.下一区间与当前区间不相交;2.下一区间与当前区间部分相交;3.下一区间在当前区间内部
        if(seg.first > end){    //第一种情况,区间不相交
            if(start != -2e9)   //去除最开始时的inf区间
                temp.push_back({start,end});    //若下一区间不与当前区间相交,则之后的区间更加不会与当前区间相交,因为是按区间起点排好序过的,所以当前区间不会再改变了,放入已合并区间中
            start = seg.first;  //更新当前区间
            end = seg.second;
        }
        else{
            end = max(end,seg.second);  //第二,三种情况
        }
    }
    if(end != 1e9)  //由于之前已合并区间的放入都是当前区间和下一区间进行比较后,若不相交才把当前区间放入已合并区间中,而最后一个当前区间没有其他区间与它进行比较,所以还需要额外把最后一个当前区间放入已合并区间中
        temp.push_back({start,end});
    segs = temp;    //将已合并区间放入答案中
}
int main()
{
    int n;
    int l,r;
    cin>>n;
    while(n--){
        scanf("%d%d",&l,&r);
        segs.push_back({l,r});  //区间起点,终点二元组的放入
    }
    merge(segs);
    cout << segs.size() << endl;
    return 0;
}

区间类型题套路写法(如果不用记录具体区间的话res都不用vector,直接int就行):

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10, inf = 0x3f3f3f3f;
struct side{
    int l, r;
    bool operator < (const side & t) const{
        if(l < t.l || (l == t.l && r <= t.r)) return 1;
        return 0;
    }
}s[N];
vector<side> res;
int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; ++ i) scanf("%d%d", &s[i].l, &s[i].r);
    sort(s, s + n);
    int st = -inf, ed = -inf;
    for(int i = 0; i < n; ++ i){
        if(s[i].l > ed){
            if(st != -inf) res.push_back({st, ed});
            st = s[i].l;
            ed = s[i].r;
        }
        else ed = max(ed, s[i].r);
    }
    if(ed != -inf) res.push_back({st, ed});
    cout << res.size() << endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值