写在最前面:

此系列中的所有模板全为大佬所写,我只是一个搬运工(?)。

记录

双指针

双指针其实平常用到的很多(我之前用过也不知道叫双指针,,,大意了),也算是非常常用的一种算法。主要的作用是节省时间,把平常需要双循环的朴素算法(时间复杂度O(n^2))根据i和j之间的关系简化成双指针算法(时间复杂度O(n^2)),从而避免时间超限的后果。一般常见的双指针有两种,一种是两个指针分别指向两个数组,一种是两个指针指向同一个数组(这个更为常见)。

双指针的核心是找到双循环里面i和j的关系,所以可以先把题目用双循环写出,然后在简化为双指针的方法。

下面是模板:

#include <iostream>

using namespace std;
bool check(int i,int j)//由题意决定,这里就是随便写了一个方便编译
{
    if(i>j)
        return true;
    return false;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int j=0;//根据题意而定
        while(j<n&&check(i,j))//j的具体范围&&题目判断条件
            j++;
        //根据题意决定下面的具体内容
    }
    return 0;
}

位运算

大部分都是由简单的符号完成的。

下面是常见的运算符号:

"&" 与(11为1,其余为0)

"|" 或(00为0,其余为1)

"^" 异或(相同0不同1)

"~" 取反(0->1,1->0)

"<<"左移(二进制位左移,e.g n<<k表示n * 2^k)

">>"右移(二进制位右移,e.g n>>k表示n / 2^k)

题目如 n的二进制表示下第k为是几。

解题过程如下:

  1. 把第k位排到最后一位 n>>k(n不用化为二进制)
  2. 读取k x&1(与运算,11为1,10为0)

下面是该题目模板:

#include <iostream>
#include <cstdio>
using namespace std;
int lowbits(int x)
{
    return x&-x;
}
int main()//计算二进制数里面1的个数
{
    int n;
    int a;
    scanf("%d",&n);
    while(n--)
    {
        int num=0;
        scanf("%d",&a);
        while (a) a-=lowbits(a),num++;
        printf("%d ",num);
    }
    cout << endl;
    return 0;
}

离散化

题目如:先输入n个a b,把在数轴上a对应的位置+b(本来都为0),然后给定k个区间,算每个区间内的总和(这其实是离散化+前缀和)

离散化就是把很大范围但是很分散的数据用新的容器来统装起来,按照出现的顺序排序编号。

解决过程如下:

  1. 把原数组去重。
  2. 寻找x所对应的离散化后的位置。

下面是例题模板:

#include <iostream>
#include <vector>//相当于一个功能更多的数组
#include <algorithm>


using namespace std;
typedef pair<int,int> range; //pair对象可以存储两个值,这两个值可以是不同的数据类型。
const int N=1e5*3+10;
int n,m;
int a[N],s[N];

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

int find1(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(auto item:add)//c++特性遍历,遍历整个add
    {
        int x=find1(item.first);//x离散化之后的值
        a[x]+=item.second;//在离散化之后的坐标上加上要加的数

    }

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

    //处理询问
    for(auto item:query)
    {
        //item.first:左区间离散化之后的值。item.second:右区间离散化之后的值
        int l=find1(item.first),r=find1(item.second);
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}

(离散化折磨了我很久,,,蚌埠住了。)

区间合并

题目如:给你n个区间把它们有交集的合并,求最少区间数。

题目解决过程如下:(和贪心里面的区间覆盖类似) 把区间按照左端点从小到大排序。 比较当前区间右端点和下一个区间里左端点,如果当前区间右端点大,则可以把下一个区间放进来,同时该区间的右端点更新为两个区间右端点中较大的一方。否则,开下一个新区间,并以这里所指的下一个区间当做样例。进行递归求解。

下面是模板:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef  pair<int,int> PII;
vector<PII> segs;
int n;

void merge1(vector<PII> &segs)
{
    vector<PII> tmp;
    sort(segs.begin(),segs.end());//pair默认的按照左端点排序再按照右端点排序
    int st=-2e9,ed=-2e9;
    for(auto item:segs)//c++特有遍历
    {
        if(ed < item.first)//如果ed小于该区间最小值那么就开一个新区间
        {
            if(st!=-2e9) tmp.push_back({st,ed});//避免把最初的区间录入进去
            st = item.first;
            ed = item.second;
        }
        else ed=max(ed,item.second);
    }
    if(st!=-2e9) tmp.push_back({st,ed});

    segs=tmp;
}

int main()
{
    cin>>n;
    for(int i=0; i<n; i++)
    {
        int l,r;
        cin>>l>>r;
        segs.push_back({l,r});
    }
    merge1(segs);
    cout << segs.size() << endl;
    return 0;
}

over.