双指针算法
前面的快排和归并有用到...
主要有两种:①两个指针在同一个序列中②链各个指针分别在一个序列
常见样式:
for(int i=0,j=0;i<n;i++){
while(j<i && check(i,j))
j++;
//每道题目的具体逻辑
}
核心用途就是将朴素算法(n^2)优化到O(n)
例题1:有一个形如abc def hij的字符串,请输出每个单词(不含空格),每个单词占一行
输入:
abc def ghi
===============
输出:
abc
def
ghi
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;//定义字符串
getline(cin,str);
//cout<<str<<endl;
int n=str.size();//字符串长度
for(int i=0;i<n;i++){
int j=i;
while(j<n && str[j]!=' ') j++;
//问题的具体逻辑
for(int k=i;k<j;k++) cout<<str[k];
cout<<endl;
i=j;
}
return 0;
}
思考:
暴力做法:先列举终点再起点,令i是终点,j是起点,判断一下j到i是否成立
//暴力做法 O(n^2)
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
if(check(j,i)){
res=max(res,i-j+1);
}
}
}
优化思考:观察i,j的规律,发现只要i向后走,j不可能向前,具有单调性
所以只要枚举i,check就是检查i,j区间 没有重复数字,若重复,j需要向后走直到区间内无重复元素,时间复杂度就到O(n)
//双指针算法:O(n)
for(int i=0;i<n;i++){
//check查看j,i之间有没有重复元素
//只要j和i之间有重复元素,就把j向后移动一位,直到j和i之间没有重复元素
while(j<=i && check(j,i)) j++;
res=max(res,i-j+1);
}
#include<iostream>
using namespace std;
const int N = 1e5+10;
//S是记录区间每个数组出现多少次,一旦大于1说明出现了重复元素,而且一定是a[i]
int s[N],a[N];
int main(){
int n;
cin>>n;
for(int i = 0 ;i < n ; i ++) cin>>a[i];
int res = 1;//最长连续不重复子序列的长度
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;
}
位运算
常见应用①:求n的二进制表示中第k位
从最低位开始数,最低位是第0位,先让x的二进制右移k位,再和1的二进制数进行与运算;
比如n=101010000,k=4,就是要取101010000的第四位数字也就是1,先右移4位成10101,再和00001与运算得1,任何数与1进行与运算都得最低位
又如:n=15=( 1111 ) 2
1.先把第k位移到最后一位 :n>>k 2.看一下个位是几 :x&1
->所以公式为:n>>k&1
常见应用②:lowbit(x),作用是返回x的最后一位1
返回n的二进制的从最低位开始数的第一位1及低位的所有数字
(比如101010000就是返回10000),如果 x 的二进制表示是正数,那么 -x 的二进制表示就是 x 的补码,即x与x得补码进行与运算,比如101010000,即101010000&010110000,得10000
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[N];
int lowbit(int x){
return x&-x;
}
int main(){
int n;
cin>>n;
for(int i = 0 ; i< n ; i++) cin>>a[i];
for(int i = 0 ; i< n ; i++)}
int t = 0;
while(a[i]){
a[i]-=lowbit(a[i]);//每次减去最后一位1
t++;
}
cout<<t<<" ";
}
cout<<endl;
}
回顾原码、反码、补码
x=1010
原码:1010; 反码:0101 ;补码(取反加1):0110
离散化
(整数的有序的离散化)
一般是值域跨度很大,但是数量不多,比较稀疏的一些数值,把这些数值序列映射到从0或1开始的自然数,这个过程就是离散化。
可能会存在:①原来的数值可能存在重复元素,需要去重;(先排序,利用库函数)
②需要将原来的数值离散,那么如何计算(因为原数值都是有序的,可以根据下标当作它离散化后的值,所以二分查找就可以)
//离散化模板
vector<int> alls;//存储所有待离散化的值
sort(alls.begin(),alls.end();//将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());//去掉重复元素
//二分求出x对应的离散化的值
int find(int x)//找到第一个大于等于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;//映射到1,2,.n,因为前缀和一般下标是从1开始的
}
对整数x加上c就是:先求出x离散化后的值k,然后在离散化后的序列上进行a[k]+=c
在求[l,r]之间所有数的和时,先把l和r离散化到对应的下标位置 ,再求区间和(前缀和)
#include<iostream>
#include<vector>//用于做离散化
#include<algorithm>
using namespace std;
const int N = 3e5+10;
//用pair存放x和c以及l和r
typedef pair<int,int> PII;//pair是将2个数据组合成一组数据
int a[N],s[N];//a[N]是存储插入坐标的值的数组,并且插入坐标和a的索引相对应,s是a的前缀和数组
vector<int> alls;//alls是存储所有坐标的数组
vector<PII> add,query;//add是存储插入坐标和插入值的容器,query是存储查询坐标的容器
//返回alls数组中x值的位置 ,二分
int find(int x){
int l = 0, r = alls.size() -1;
while(l<r){
int mid = l+r>>1;
if( x <= alls[mid]) r = mid;
else l = mid+1;
}
return r+1;//让映射后的数组从1开始
}
int main(){
int n,m;
cin>>n>>m;
while(n--){
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x);
}
while(m--){
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()); //去重
//根据alls中存储的坐标将要加的值映射到a数组中 插入
for(int i = 0; i < add.size();i++){
int x = find(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 = find(query[i].first);
int r = find(query[i].second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
区间合并
有很多个区间,当多个区间有交集,可以把它们合并成同一个区间
思路:
①先按照左端点的排序规则对所有区间进行排序
②然后维护一个区间a【st,ed】,a后面的区间b和区间a可能有三种关系
区间b不可能在区间a前面,即区间b的左端点st一定小于等于区间a的左端点,这是因为我们已经按照左端点的排序规则进行排序了。
情况1(包含),我们维护的区间不变,不需要操作;
情况2(有交集),我们将区间a延长。
情况3(没有交集),我们就可以把区间a放入答案中去,并将区间更新为b
当区间b和区间a没有交集时,说明区间b以及区间b后面的所有区间都不和a相交了,我们就将维护的区间更新为区间b
//区间合并模板
//将所有存在交集的区间合并
void merge(vector<PII> &segs){
vector<PII> res;
sort(segs.begin(),segs.end());
int st = -2e9, ed = -2e9;
for(int i = 0 ; i< segs.size(); i++){
if(segs[i].first > ed){
st = segs[i].first,ed = segs[i].second;
res.push_back({st,ed});
}
else ed = max(ed,segs[i].second);
}
segs = res;
}
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e5+10;
typedef pair<int,int> PII;
void merge(vector<PII> &segs){
vector<PII> res;
sort(segs.begin(),segs.end());//所有区间排序
int st = -2e9, ed = -2e9;//边界
//扫描所有线段
for(int i = 0 ; i< segs.size(); i++){
//无交集
if(segs[i].first > ed){
st = segs[i].first,ed = segs[i].second;
res.push_back({st,ed});
}
//求并集
else ed = max(ed,segs[i].second);
}
//还要把最后的区间加到答案里去
if(st!=-2e9) res.push_back({st,ed});
segs=res;
}
int main(){
vector<PII> segs;
int n;
cin>>n;
while(n--){
int l,r;
cin>>l>>r;
segs.push_back({l,r});//读入
}
merge(segs);//合并
cout<<segs.size()<<endl;//返回合并后的序列长度
return 0;
}