day04.双指针+位运算+离散化+区间合并

一、双指针(两类)

双指针算法的核心是将O(n^2)的算法优化到O(n)。
在这里插入图片描述

  • 类别一:两个指针维护两个序列(如 归并)
    在这里插入图片描述
  • 类别二:两个指针维护一个序列–首尾(如 快排)
    在这里插入图片描述

1.思想

① 遍历
② 更新

2.模板

for (int i = 0, j = 0; i < n; i ++ )  //i从0开始,j从某一点开始
{
    while (j < i && check(i, j))   //每次i更新完,就要更新j
    	j ++ ;                   //前提是 j在合法范围内,且满足某种性质

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

3.题目

(1)将一串英文字符按单词分隔开,单词间以空格分割。
如:
输出: abc dfg
输出:abc
dfg

在这里插入图片描述

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

int main() {
	char str[100];
	gets(str);      //读取一行文本并存储在str中
	int n = strlen(str); //计算以 null 字符 '\0' 结束的字符串的长度
	for (int i = 0; i < n; i++) {
		int j = i;
		while (j < n && str[j] != ' ') {
			cout << str[j];
			j++;
		}
		cout << endl;

		i = j;
	}
	retur 0;
}

(2)799.最长连续不重复子序列
在这里插入图片描述
在这里插入图片描述

const int N=10010;
int n;
int a[N],s[N];

int main() {
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
		
	int res=0;
	for(int i=0,j=0;i<n;i++){    // j表示滑动窗口的左边界
		s[a[i]]++;     // 存储每个数 出现次数 的数组
		
		// 如果当前数字出现次数大于 1,说明有重复,调整左边界
		while(s[a[i]]>1){  
			s[a[j]]--;  //减少左边界指向的数字出现的次数
			j++;        // 左边界右移
		}
		res=max(res,i-j+1);
	}
	cout << res <<endl;
	return 0;
}

二、位运算

1.思想&&模板

  • 求n的第k位数字: n >> k & 1
    在这里插入图片描述
int main() {
	int n = 10;      // 10的二进制表示
	for (int k = 3; k >= 0; k--)
		cout << (n >> k & 1);     // 1010
}
  • 返回n的最后一位1:lowbit(n) = n & -n
    在这里插入图片描述
int lowbit(int x){
	return x&-x;
}

2.题目(801.二进制中1d个数)

在这里插入图片描述

int lowbit(int x){
	return x&-x;
}

int main() {
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		
		int res=0;    //存储结果
		//只要x不为0,就减去lowbit,减多少次就有多少个1;
		while(x){
			x=x-lobit(x);
			res++;
		}
		cout<<res<<endl;		
	}
	return 0;
}

三、离散化

若有一个数组范围很大,但元素很少,在保证相对位置不变的前提下,则可以将其下标映射到一个新数组中,减少空间的浪费。
在这里插入图片描述

1.思想&&模板

① 数组去重—利用库函数可以实现 排序+去重

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
// unique会将重复元素置于数组末尾,并返回独一无二元素中最后一个元素的下标。
//erase 再将unique的返回值作为起点,删除末尾的元素 即完成去重。
  • 关于unique方法的实现(利用双指针)
    在这里插入图片描述
vector<int>::iterator unique(vector<int> &a){
	int j=0;      // 用于追踪新数组中的元素,即独一无二的元素
	for(int i=0;i<a.size();i++)
		if(!i||a[i]!=a[i-1])   //不是第一个元素 且 不与前一个元素相同(不重复)
			a[j++]=a[i];       //可以理解为加入新数组
	// a[0]~a[j-1] 所有a中不重复的数
	
	return a.begin()+j;	
}

② 二分求出 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 表示从1开始映射
}

2.题目(802.区间和)

在这里插入图片描述
分析:
(1)n次操作,m次问询,由于每次问询设计两个位置,故该题共涉及n+2 * m个数。
核心:在保证这n+2 * m个数字在相对位置不变的情况下,将他们存入数组,去重排序作为基准,便于进行离散化
tips:由于1<=n,m<=10000,即最多进行10000次操作和10000次问询,n+2 * m<=30000。故存储这n+2 *m个位置的数组的容量应设置为30000个,同时避免re,令N=30010。

(2)将n次操作中的位置 x 进行离散化,更新数轴。
(3)m次询问中的位置l,r进行离散化,求前缀和。

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

typedef pair<int,int> PII;    // 定义双元数组
const int N=300010;    //n+2m

int n,m;              // n次操作,m次问询
int a[n],s[n];        // a数组模拟数轴,全为0; s数组 存储前缀和

vector<int> alls;       // 存储 x,l,r 的数值
vector<PII> add,query;  // 数组存储 ①操作参数,②问询参数

int find(x){    // 找大于x的最小值
	int l=0,r=a.size()-1;
	while(l<r){
		int mid = l+(r-l)/2;
		if(alls[mid]>=x)
			r=mid;
	}
	return r+1;     //	
}

int main() {
	cin >> n >> m;            // 1.1
	for(int i=0;i<n;i++){     // 1.2 读入操作参数
		int x,c;
		cin >> x >> c;
		add.push_back({x,c})
		
		alls.push_back(x);     // 单独存储 操作 中的 位置参数 x
	}
	for(int i=0;i<m;i++){     // 1.3 读入问询参数
		int l.r;
		cin >> l >> r;
		query.push_back({l,r});
		
		alls.push_back(l);
		alls.push_back(r);		
	}
//----------------------------------------------
	// 2.去重
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	
	// 3.处理 操作步骤
	// 3.1 更新
	for(auto item:add){
		int x = find(item.first);     // 对 x 离散化
		a[x] = a[x] + item.second;    // 某一位置x上的数+c
	}
	// 3.2 处理前缀和,为 询问 做准备
	for (int i=0;i<alls.size();i++){
		s[i]=s[i-1]+a[i];
	}
	
	// 4.处理询问
	for (auto item:query){
		int l=find(item.first);
		int r=find(item.second);
		cout << s[r]-s[l-1] << endl;
	}

	return 0;
}

四、区间合并

把有交集的区间合并。

1.思想

(1)按照区间的左端点排序
(2)区间与区间的关系有三种:包含,相交,不相交。
① 包含则不用管;
② 相交则合并;
③ 不相交则不用合并。

  • 综合来说,包含 和 相交 的处理原则可以合并:左端点不变,将右端点更新为 两个区间右端点的最大值
  • ③ 不相交时,则将当前区间加入结果区间,将下一个不相交区间作为当前区间。
    在这里插入图片描述

2.模板

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            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;
}

3.题目(803.区间合并)

在这里插入图片描述

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

const int N = 100010;

typedef pair<int, int> PII;   // 定义双元数组,便于存储区间。
vector<PII> segs;

void merge(vector<PII> &sges) {
	vector<PII> res;    // 结果数组

	sort(segs.begin(), segs.end()); //会先按照第一关键字排序,相同时再按照第二关键字排序。

	int st = -2e9, ed = -2e9;   // 当前区间的左端点,右端点
	for (auto seg : segs) {
		if (seg.first > ed) {  // 情况3:不相交
			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 = 0; i < n; i++) {
		int l, r;
		cin >> l >> r;
		segs.push_back({l, r});
	}

	merge(segs);

	cout << segs.size() << endl;
	return 0;
}

五、贪心区间问题

5.1 (AcWing 905.区间选点)

题意:从N个闭区间中选出最少的点覆盖所有区间。
思想:

  1. 将所有区间的右端点 从小到大 排序
  2. 枚举每一个区间:
    ① 若当前区间中已经包含点,则跳过该区间。
    ② 否则,选点并更新右端点为当前区间的右端点。

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;

// 定义区间结构体
struct Range
{
    int l,r;
    bool operator< (const Range &W)const
    {
        return r<W.r;
    }
}range[N];
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int l,r;
        cin>>l>>r;
        range[i]={l,r};
    }
    
    // 1.排序
    sort(range,range+n);

	// 2.遍历
    int res=0,ed=-2e9;   //res表示选点的数量,ed为右端点
    for(int i=0;i<n;i++)
        if(range[i].l > ed)    // 该区间与上一区间 未重合
        {
            res++;          // 选点
            ed = range[i].r;  // 更新右端点
        }

    cout<<res;
    return 0;
}

5.2 (AcWing 908.最大不相交区间数量)

同上一题一样,选出来点的数量==最大不相交区间的数量。
在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
struct Range
{
    int l,r;
    bool operator< (const Range &W)const
    {
        return r<W.r;
    }
}range[N];
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int l,r;
        cin>>l>>r;
        range[i]={l,r};
    }
    sort(range,range+n);
    int res=0,st=-2e9;
    for(int i=0;i<n;i++)
        if(range[i].l>st)
        {
            res++;
            st=range[i].r;
        }
    cout<<res;
    return 0;
}

5.3 (AcWing 906.区间分组)

1.将所有区间按照 左端点从小到大 排序
2. 遍历所有的区间:使用 小根堆 存储每个组最后一个区间的右端点值
① 如果当前区间的 左端点 <= 堆中最小右端点 (相交),说明该区间不能放进任何一个分组,则将这个区间加入堆**(新开一组)
② 如果当前区间左端点 > 堆中最小右端点
(不相交),说明该区间能够放进任意一个组中,则将其放入右端点最小的组。将右端点最小的组的右端点更新**

在这里插入图片描述

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
const int N=100010;
struct Range
{
    int l,r;
    bool operator <(const Range &W)const
    {
        return l<W.l;
    }
}range[N];
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int l,r;
        cin>>l>>r;
        range[i]={l,r};
    }

	// 1.排序
    sort(range,range+n);
    priority_queue<int ,vector<int> ,greater<int> > heap;   //小顶堆

	// 2.遍历
    for(int i=0;i<n;i++)
    {
        if(heap.empty()||heap.top()>=range[i].l)   // ① 相交
        	heap.push(range[i].r);
        else
        {
            heap.pop();       // 起到更新 最小右端点的作用。
            heap.push(range[i].r);
        }
    }
    
    //堆中有多少个最小右端点,就有多少个组。
    cout<<heap.size();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值