【算法笔记】离散化,区间合并,数组模拟单双链表

算法笔记

1.离散化

1.1 解决的问题:

把范围较大但是数据较少的区域映射到一个范围较小,相对连续的的区域内。(也就是值域跨度很大,但是数据分布非常稀疏)假设这个范围较大的数据标记为数组a[],在解决离散化问题的过程中需要解决的问题:

a. a[]中可能出现重复元素,所以我们要去重

b. 如何算出a[i]离散化之后的数是多少。(可以使用二分)

1.2 解决方案:

对于去重:先unique一边,unique的过程是将重复元素放入a数组的尾端,然后返回非重复部分的最后一个端点的下标。最后我们只需要erase掉最后一段重复的元素即可。
请添加图片描述

1.3 整体思路

首先,一个下标对应一个数。在插入阶段,我们在一个无穷大的数轴上根据下标插入多个数。比如我们在下标为i的位置上插入c这个数字。最终插入完成后,有多少个下标就有多少个数字。由于这个下标的范围可能很大,但是数字个数是有限的,所以如果我们能找到一个对应关系,把这些下标映射到一个有限长的区间内,就可以使用前缀和的方法解决本类问题了。

另外,在询问阶段,我们需要根据俩个下标来进行询问,我们假设下标是l和r表示询问l到r之间的元素的总和。这个l和r也需要对应成对应的下标。

举例:比如在原始的无限长的数轴上。我们在下标为1的位置上插入100,在下标为10000的位置上插入5;现在我们询问原始数轴上从0到10001的位置上所有元素的总和。我们就可以这样映射:

原始下标:0 1 10000 1001

映射下标:1 2 3 4

对应元素:0 100 5 0

这样,对原始数组从0到1001范围内数据求前缀和的问题就转化成了对映射数组从1到4范围内求前缀和。把一个较大的无限区间转化为一个较小的有限区间。

例题:ACwing802 区间和:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
typedef pair<int,int> PII;
vector<PII>ins,qurry;
vector<int>index;
const int N = 300010;
int a[N];
int s[N];

int find_(int x){
	
	int low=0;
	int high=index.size()-1;
	while(low<high){
		
		int mid = (low+high)/2;
		if(index[mid]>=x)high=mid;
		else low=mid+1;
	}
	return low+1;
	
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;++i){
		int x,c;
		cin>>x>>c;
		ins.push_back({x,c});
		index.push_back(x);
		
	}
	for(int i=0;i<m;++i){
		int l,r;
		cin>>l>>r;
		qurry.push_back({l,r});
		index.push_back(l);
		index.push_back(r);
	}
	sort(index.begin(),index.end());
	index.erase(unique(index.begin(),index.end()),index.end());
	for(int i=0;i<ins.size();++i){
		int item=find_(ins[i].first);
		a[item]+=ins[i].second;
		
	}
	for(int i=1;i<=index.size();++i){
		s[i]=s[i-1]+a[i];
	} 
	
	for(int i=0;i<qurry.size();++i){
		int l=find_(qurry[i].first);
		int r=find_(qurry[i].second);
		cout<<s[r]-s[l-1]<<endl;
	}
	
	return 0;
}

2. 区间合并

2.1 解决的问题:

若有俩个区间A和B,区间合并需要解决的问题就是若AB中有重复的元素,则在合并AB的时候将AB之间重复的范围只保留一次,相当于求A∪B。简而言之就是,把所有有交集的区间合并,合并完成后的新区间是没有任何交集的。

请添加图片描述

如图,将五个蓝色的区间用一种比较快的算法合并为俩个绿色的空间,就是区间合并。

  • 关于区间合并的边界问题的规定:如果俩个区间只有端点位置相交,我们也算他们可以合并。

2.2 思路

a. 按所有区间的左端点排序

b. 扫描整个区间,扫描的过程中,将整个可能有交集的区间合并。此时可能出现的情况如下:

请添加图片描述

  • 第一种情况为:有交集,但是完全交于内部

  • 第二种情况为:有交集,且合并后区间大于原来的区间

  • 第三种情况为:无交集。

    例题:ACwing803 区间合并

    #include<iostream>
    using namespace std;
    #include<vector> 
    #include<algorithm>
    
    typedef pair<int,int> PII;
    vector<PII> seg;
    
    vector<PII> Merge_(vector<PII>& seg){
    	
    	vector<PII> C;
    	if(seg.size()==0)return C;
    	
    	if(seg.size()==1){
    		
    		C.push_back(seg[0]);
    		return C;
    	}
    //	int start=2e-9;
    //	int end=2e-9;
        int start=seg[0].first;
        int end=seg[0].second;
        for(int i=1;i<seg.size();++i){
        	if(seg[i].first<=end){
        		end=max(seg[i].second,end);
    		}else{
    			C.push_back({start,end});
    			start=seg[i].first;
    			end=seg[i].second;
    		}
    	}
    	C.push_back({start,end});
    	return C;
    }
    
    int main(){
    	
    	int n;
    	cin>>n;
    	for(int i = 0;i<n;++i){
    		int l,r;
    		cin>>l>>r;
    		seg.push_back({l,r});
    	}
    	
    	sort(seg.begin(),seg.end());
    	
    	vector<PII> C= Merge_(seg);
    	
    	cout<<C.size()<<endl;
    	return 0; 
    }
    

3. 模拟链表与邻接表

3.1 用数组模拟单链表

邻接表最常用的是:存储图和树

请添加图片描述

为什么要使用数组实现链表? 因为快!

#include <iostream>
using namespace std;
const int N = 100010;
int e[N];
int next_[N]; 
int head=-1;//表示链表的头节点 
int idx=0;//表示一个空节点 

//单链表的头插法 
//在表头插入一个x 
void add_head(int x){
	e[idx]=x;
	next_[idx]=head;
	head=idx;
	idx++;
}
//删除第K个插入的节点后面的数

void remove(int k){
	next_[k-1]=next_[next_[k-1]];
	
} 
//在第K个插入的数后面插入一个数字
void insert_k(int k,int x){
	e[idx]=x;
	next_[idx]=next_[k-1];
	next_[k-1]=idx;
	idx++;	 
} 

int main(){
	
	int m;
	cin>>m;
	while(m--){
		char c;
		int x,k;
		cin>>c;
		if(c=='H'){
			cin>>x;
			add_head(x);
			continue;
		}
		if(c=='D'){
			cin>>k;
			if(k==0){
			head=next_[head];
			continue;
		    }else{
		    	remove(k);
			}
			continue;
		}
		cin>>k>>x;
		insert_k(k,x);
	}
	
	for(int i=head;i!=-1;i=next_[i])cout<<e[i]<<" ";
	cout<<endl;
	return 0;
}

3.2 双链表:用来优化某些问题

数组模拟双链表,用一个L数组记录下标为idx的做指针,R记录右指针。【这道题的代码可以优化,我写的代码思路并不好】我们让下标为0的的索引始终指向表头,下标为1的索引始终指向表尾。

仔细体会链表的代码我们可以知道,在用数组模拟链表的过程中,下标就相当于链表中的指针。e[idx]就相当于元素的value

PS:↑仔细体会上面的这句话有助于理解模拟的过程

#include<iostream>
using namespace std;
const int N=100010;
int L[N];
int R[N];
int e[N];
int front=0;
int end_=0;
int idx=0;
//初始化双链表 
void init_(){
	front=0;
	end_=1;
	R[front]=end_;
	L[end_]=front;
	idx=2;	
} 
void delet_k(int k){
	R[L[k+1]]=R[k+1];
	L[R[k+1]]=L[k+1];
}
//在第K个插入的数的右侧插入一个数。
void insert_right(int k,int x){
	e[idx]=x;
	R[idx]=R[k+1];
	L[R[k+1]]=idx;
	L[idx]=k+1;
	R[k+1]=idx;
	idx++;
} 
int main(){
	int m;
	cin>>m;
	init_();
	while(m--){
		string op;
		int k,x;
		cin>>op; 
		if(op=="L"){
			cin>>x;
			insert_right(-1,x);
			continue;
		}
		if(op=="R"){
			cin>>x;
			insert_right(L[1]-1,x);
			continue;
		}
		if(op=="D"){
			cin>>k;
			delet_k(k);
			continue;
		}
		if(op=="IL"){
			cin>>k>>x;
			insert_right(L[k+1]-1,x);
			continue;
		}
		cin>>k>>x;
		insert_right(k,x);
		
	}
	for(int i=R[0];i!=1;i=R[i])cout<<e[i]<<" ";
	
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值