算法笔记
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;
}