一、快速排序
1.1、模板
#include<iostream>
using namespace std;
const int N = 1e5+10;
int q[N];
int n;
void quick_sort(int q[],int l,int r){
int mid = q[(l+r)/2];
int i = l,j = r;
int temp;
while(i<j){
while(q[i]<mid)i++;
while(q[j]>mid)j--;
if(i<=j){
temp = q[i];
q[i] = q[j];
q[j] = temp;
i++;
j--;
}
}
if(l<j)quick_sort(q,l,j);
if(i<r)quick_sort(q,i,r);
}
int main(){
int array[]={49,38,65,97,76,13,27,49};
int length=sizeof(array)/sizeof(*array);
cout<<"原始序列:";
for(int i=0;i<length;i++){
cout<<array[i]<<" ";
}
cout<<endl;
quick_sort(array,0,length-1);
cout<<"快排序列:";
for(int i=0;i<length;i++){
cout<<array[i]<<" ";
}
return 0;
}
1.2、思路
第一步:确定一个分界点mid(建议取中间数)
第二步:调整区间,让mid左边的数都<=mid,mid右边的数都>=mid
第三步:递归处理左右两边
1.3、例题1:第k个数
答案1:
#include<iostream>
using namespace std;
const int N=1e5;
int a[N];
void quick_sort(int a[],int l,int r){
int mid = a[(l+r)/2];
int i=l,j=r;
int temp;
while(i<j){
while(a[i]<mid)i++;
while(a[j]>mid)j--;
if(i<=j){
temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
j--;
}
if(j>l)quick_sort(a,l,j);
if(i<r)quick_sort(a,i,r);
}
}
int main(){
int n,k;
cin>>n>>k;
int p[n];
for(int i=0;i<n;i++){
cin>>p[i];
}
quick_sort(p,0,n-1);
cout<<p[k-1]<<endl;
return 0;
}
二、归并排序
2.1、模板
#include<iostream>
using namespace std;
const int N = 1e5+10;
int n;
int q[N],temp[N];
void merge_sort(int q[],int l,int r){
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
{
if (q[i] <= q[j]) temp[k++] = q[i++];
else temp[k++] = q[j++];
}
while (i <= mid) temp[k++] = q[i++];
while (j <= r) temp[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++)
q[i] = temp[j];
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>q[i];
}
merge_sort(q,0,n-1);
for(int i=0;i<n;i++){
cout<<q[i]<<" ";
}
return 0;
}
2.2、思路
归并排序的思想是将一个大的序列先进行二分法划分,直到划分到每个字序列只包含一个元素为止。然后对序列进行依次合并,在合并的过程中实现排序。
归并排序是一种典型的外部排序算法,需要额外的辅助空间来完成算法。
2.3、例题1:求逆序对
题目如下
给定一个长度为n的整数数列,请你计算数列中的逆序对的数量。逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数n,表示数列的长度。第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。数据范围
1≤n≤100000
输入样例
6
2 3 4 5 6 1
输出
5
答案:
#include<iostream>
using namespace std;
#include<algorithm>
const int N = 1e5+10;
int q[N];
int n;
int temp[N];
long long ans;
void merge_sort(int l,int r,int q[]){
if(l>=r)return;
int mid = l+r>>1;
merge_sort(l,mid,q);
merge_sort(mid+1,r,q);
int i=l,j = mid+1,k=0;
while(i<=mid && j<=r){
if(q[i]>q[j]){
temp[k++] = q[j++];
ans+=mid-i+1;
}
else
temp[k++] = q[i++];
}
while(i<=mid)temp[k++] =q[i++];
while(j<=r)temp[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++)
q[i] = temp[j];
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>q[i];
ans =0;
merge_sort(0,n-1,q);
cout<<ans<<endl;
return 0;
}
三、二分查找
3.1、模板
3.1.1、模板一
将区间[l,r]
划分为[l,mid]
和[mid+1,r]
时,其继续查找的操作是r=mid
或者l=mid+1
,计算mid的时候不需要额外加一。
#include<iostream>
using namespace std;
inr binarySearch1(int l,inr ){
while(l<r){
int mid = l+r>>1;
if(check(mid))r = mid;//假设这个数一开始在左边
else l = mid+1;
}
return l;
}
3.1.2、模板二
将区间[l,r]
划分为[l,mid-1]
和[mid,r]
时,其继续查找的操作是r=mid-1
或者l=mid
,为了防止进入死循环,计算mid的时候需要额外加一。
#include<iostream>
using namespace std;
int binarySearch2(int l,int r){
while(l<r){
int mid = l+r+1>>1;
if(check(mid)) l = mid;//假设这个数在右边
else r = mid-1;
}
return l;
}
3.1.3、浮点数二分查找
#include<iostream>
using namespace std;
bool check(double x){
}
//eps 表示精度,取决于题目对精度的要求 比有效数精度多2
double binarySearch3(double l,double r){
const double eps = 1e-6;
while(r-l>eps){
double mid = l+r>>1;
if(check(mid))r = mid;
else l = mid;
}
return l;
}
3.2、思路
将n个元素分成个数大致相同的两半,取q[n/2]与想要查找的元素x作比较,如果找到算法终止。如果没有找到,则分为两种情况:
- x>q[n/2],则在数组q的右半部继续搜索x;
- x<q[n/2],则在数组q的左半部继续搜索x;
二分查找的模板一共有两个,视情况进行选择。
3.3、例题
3.3.1、例题1:数的范围
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000输入样例:
6 3
1 2 2 3 3 4
3
4
5输出样例:
3 4
5 5
-1 -1
#include<iostream>
using namespace std;
int n,q,k;
const int N = 1e5+10;
int a[N];
int main(){
cin>>n>>q;
for(int i=0;i<n;i++){
cin>>a[i];
}
while(q--){
cin>>k;
int l=0,r = n-1;
while(l<r){
int mid=l+r>>1;
if(k>a[mid])l=mid+1;
else
r = mid;
}
if(a[l]!=k)
cout<<-1<<" "<<-1;
else{
cout<<l<<" ";
int l =0,r = n-1;
while(l<r){
int mid = 1+l+r>>1;
if(k<a[mid]){
r = mid-1;
}
else
l = mid;
}
cout<<l<<endl;
}
}
return 0;
}
3.3.2、例题2:数的三次方根
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。输出格式
共一行,包含一个浮点数,表示问题的解。注意,结果保留 6 位小数。
数据范围
−10000≤n≤10000输入样例:
1000.00输出样例:
10.000000
解析:
求n的三次方根,n的范围是[-10000,10000],所以答案and的范围可以初始化为[-10000,10000]
但是,求n的二次方根,n的范围是[0,0.01],答案ans 的范围不能初始化为[0,0.01]
浮点数比较问题:
如果两个浮点数的差的绝对值小于一个很小的数。比如题中的1e-8
,因为题目要求小数点后保留6个以上数字。
#include<iostream>
using namespace std;
int main(){
double n;
double l = -10000,r = 10000;
cin>>n;
while(r-l>=1e-8){
double mid = (r+l)/2;
if(mid*mid*mid>=n)r= mid;
else l = mid;
}
cout<<l<<endl;
return 0;
}
四、高精度模板
4.1、高精度加法
位数>=10^6
#include<iostream>
#include<vector>
using namespace std;
vector<int> add(vector<int> &A,vector<int> &B){
vector<int> C;
int t=0;//进位
for(int i=0;i<A.size() || B.size();i++){
if(i<A.size())t+=A[i];
if(i<B.size())t+=B[i];
C.push_back(t%10);
t/=10;
}
if(t) C.push_back(t);
return C;
}
int main(){
string a,b;
vector<int> A,B;
cin>>a>>b;
for(int i = a.size()-1;i>=0;i--)
A.push_back(a[i]-'0');
for (int i = b.size() - 1; i >= 0; i--)
B.push_back(b[i] - '0');
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--)
cout<<C[i]<<" ";
return 0;
}
4.2、高精度减法
#include<iostream>
#include<vector>
using namespace std;
// A >= B
bool cmp(vector<int> &A, vector<int> &B)
{
if (A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i--)
{
if (A[i] != B[i]) return A[i] > B[i];
}
return true;
}
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
int t = 0; // 差
for (int i = 0; i < A.size(); i++)
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--)
A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--)
B.push_back(b[i] - '0');
if (cmp(A, B))
{
auto C = sub(A, B);
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
}
else
{
auto C = sub(B, A);
printf("-");
for (int i = C.size() - 1; i >= 0; i--)
printf("%d", C[i]);
}
return 0;
}
4.3、高精度乘法
#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int> &a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size() || t; i++)
{
if (i < a.size()) t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
int main()
{
string a;
int b;
vector<int> aa;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--) aa.push_back(a[i] - '0')
auto c = mul(aa, b);
for (int i = c.size() - 1; i >= 0; i--)
printf("%d", c[i]);
return 0;
}
4.4、高精度除法
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// A/b 商是C,余数r
vector<int> div(vector<int> &a, int b, int &r)
{
vector<int> c;
r = 0;
//与加减乘不同 逆序处理
for (int i = a.size() - 1; i >= 0; i--)
{
r = r * 10 + a[i];
c.push_back(r / b);
r %= b;
}
reverse(c.begin(), c.end());
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
int main()
{
string a;
int b;
cin >> a >> b;
vector<int> aa;
for (int i = a.size() - 1; i >= 0; i--)
aa.push_back(a[i] - '0');
int r;
auto c = div(aa, b, r);
for (int i = c.size() - 1; i >= 0; i--)
printf("%d", c[i]);
// cout << endl << r << endl;
return 0;
}
五、前缀和
5.1、一维前缀和模板
s[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = s[r] - s[l - 1]
#include<iostream>
using namespace std;
const int N = 1e5+10;
int s[N],a[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<=n;i++)
s[i] = s[i-1]+a[i-1];
while(m--){
int l,r;
cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
5.2、二维前缀和模板
s[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]
#include<iostream>
using namespace std;
const int N = 1010;
int s[N][N],a[N][N];
int n,m,q;
int main(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
while(q--){
int x1,x2,y1,y2;
cin>>x1>>y1>>x2>>y2;
cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
}
return 0;
}
5.3、例题
5.3.1、例题一
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r 。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m 。第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r ,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。数据范围
1≤l≤r≤n ,
1≤n,m≤100000 ,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[N],s[N];
int main(){
int n,m,l,r;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<n;i++){
s[i] = s[i-1]+a[i-1];
}
while(m--){
cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
5.3.2、激光炸弹
地图上有 N个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。
注意:不同目标可能在同一位置。
现在有一种新型的激光炸弹,可以摧毁一个包含 R×R个位置的正方形内的所有目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。
求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。
接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。
输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
数据范围
0≤R≤1090
0<N≤100000,
0≤Xi,Yi≤5000
0≤Wi≤10000
输入样例:
2 1 0 0 1 1 1 1
输出样例:
1
#include<iostream>
#include<stdio.h>
using namespace std;
const int n = 5010;
int s[n][n];
int N,R,x,y,w;
int main(){
int maxw=0;
cin>>N>>R;
R = min(5010,R);
while(N--){
cin>>x>>y>>w;
s[x+1][y+1] += w;
}
for(int i=1;i<5001;i++)
for(int j=1;j<5001;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
for(int i=R;i<5001;i++)
for(int j=R;j<5001;j++){
maxw = max(s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R], maxw);
}
cout<<maxw;
return 0;
}
六、差分
6.1、一维差分模板
其实就是前缀和的升级版,先看一下差分函数
void insert(int l,int r,int c){
s[l]+=c;
s[r+l]-=c;
}
#include<iostream>
using namespace std;
const int N = 1e6+10;
int a[N],b[N];
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
//构造差分数组b
for(int i=1;i<=n;i++){
insert(i,i,a[i]);
}
while(m--){
int l,r,c;
cin>>l,r,c;
insert (l,r,c);
}
for(int i=1;i<=n;i++)
b[i]+=b[i-1];
for(int i=1;i<=n;i++){
cout<<b[i]<<endl;
}
return 0;
}
}
6.2、思路
详见例题
6.3、例题
6.3.1、差分
#include<iostream>
using namespace std;
const int N = 1005;
int a[N];
int l,r,c;
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i];
}
while(m--){
cin>>l>>r>>c;
for(int i=l-1;i<r;i++){
a[i] = a[i]+c;
}
}
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N],b[N];
//变化一部分数的时候即这段区间两端的b数组即差分会变化
//b[i] = a[i] - a[i - 1]
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
insert(i,i,a[i]);
}
while(m--){
int l,r,c;
cin>>l>>r>>c;
insert(l,r,c);
}
for(int i=1;i<=n;i++){
b[i]+=b[i-1];
}
for(int i=1;i<=n;i++){
cout<<b[i]<<" ";
}
return 0;
}
6.3.2、子矩阵的和
【解析】
s[i][j] = 第i行j列格子左上部分所有元素的和
以(x1,y2)为左上角,(x2,y2)为右下角
本道题是求子矩阵的和,首先按照规律将矩阵的每个点的矩阵值算出来,之后再根据相同的规矩来进行计算
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
#include<iostream>
using namespace std;
int n,m,q;
const int N = 1005;
int s[N][N];
int main()
{
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>s[i][j];
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];
}
}
while(q--){
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<s[x2][y2]+s[x1-1][y1-1]-s[x2][y1-1]-s[x1-1][y2]<<endl;
}
return 0;
}
6.3.3、差分矩阵(二维差分)
#include<iostream>
using namespace std;
const int N = 10005;
int a[N][N];
int n,m,q;
int b[N][N];
void insert(int x1,int y1,int x2,int y2,int c){
b[x1][y1]+=c;//上左边
b[x2+1][y1]-=c;//右上
b[x1][y2+1]-=c;//左下
b[x2+1][y2+1]+=c;//右下
}
int main(){
cin>>n>>m>>q;
for(int i =1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
for(int i =1;i<=n;i++)
for(int j=1;j<=m;j++){
insert(i,j,i,j,a[i][j]);
}
while(q--){
int x1,x2,y1,y2,c;
cin>>x1>>y1>>x2>>y2>>c;
insert(x1,y1,x2,y2,c);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]+=b[i][j-1]+b[i-1][j]-b[i-1][j-1];
}
}
for(int i =1;i<=n;i++){
for(int j=1;j<=m;j++){
cout<<b[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
七、双指针
7.1、模板
常见问题分类:
(1)对于一个序列,用两个指针维护一段区间
(2)对于两个序列,维护某种次序,比如归排序中合并两个有序序列的操作
for(int i=0,j=0;i<n;i++){
while(j<i&&check(i,j))j++;
res = max(res, i - j + 1)
}
7.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);
}
7.3、例题
7.3.1、最长连续不重复子序列
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续区间,输出它的长度。
输入格式
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复数字的连续子序列的长度。
数据范围
1≤n≤1000001≤n≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3
#include<iostream>
using namespace std;
const int N = 100005;
int count[N],a[N];
int n;
int main(){
cin>>n;
int res = 0;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0,j=0;i<n;i++){
//i为终点,j为起点
count[a[i]]++;
//遇到重复的元素,j往后移,同时重复的元素的个数减1
while(count[a[i]]>1)count[a[j++]]--;
//枚举从起点到终点的距离的最大值
res = max(res,i-j+1);
}
cout<<res;
return 0;
}
7.3.2、数组元素的目标和
给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。
请你求出满足A[i] + B[j] = x的数对(i, j)。
数据保证有唯一解。
输入格式
第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。
第二行包含n个整数,表示数组A。
第三行包含m个整数,表示数组B。
输出格式
共一行,包含两个整数 i 和 j。
数据范围
数组长度不超过100000。
同一数组内元素各不相同。
1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
【解析】
a与b两数组都是升序,用双指针i和j,i先指向a数组的第一个元素,j指向b数组的最后一个元素。因此,当i向右移动时a[i]变大,j向左移动时b[j]变小。
对于a 的每一个i,当a[i]+b[j]
大于目标值x时,j就向左移动直到不满足a[i]+b[j]>x
。可以将时间复杂度O(n*m)降到O(n+m)
#include<iostream>
using namespace std;
const int N = 10010;
int n,m,x;
int a[N],b[N];
int main(){
cin>>n>>m>>x;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int j=0;j<m;j++){
cin>>b[j];
}
int i=0,j=m-1;
//a数组从左边枚举,b数组从右边进行枚举
while(i<n&&j>0){
//和大于x,j向前移动
while(a[i]+b[j]>x)j--;
//和小于x,i向后移动
while(a[i]+b[j]<x)i++;
if(a[i]+b[j]==x){
cout<<i<<" "<<j;
break;
}
}
return 0;
}
八、位运算
8.1、模板
求n的第k位数字:n>>k&1
返回n的最后一位1:lowbit(n) = n & -n;
8.2、思路
n-1:一个二进制的数减1,就是将这个二进制最右边的那个1变成0,然后它后边的所有位置都变成1
让这个数n与n-1按位与再赋给它,意义是,从低位到高位,n的二进制位中第一次出现1的位置变成0(循环),直到n的二进制位上的1全变成0(此时n=0),循环终止
8.3、例题
#include<iostream>
using namespace std;
int main(){
//输入几个十进制数
int n;
cin>>n;
while(n--){
int x;
cin>>x;
int res = 0;
while(x){
x&=(x-1);
res++;
}
cout<<res<<endl;
}
return 0;
}
九、离散化
9.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
}
9.2、思路
当数据范围超过我们数组可以开出的长度,并且,我们的n和m只有五次方,也就是需要操作耳朵数最多只有3*10的五次方个数,远远小于了x的数据范围,于是我们想到了离散化,也就是将所需要操作的数进行了一个排序并且去重。
9.3、例题
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
// 存储所有下标
vector<int> alls;
// 存储添加 查询操作
vector<PII> add, query;
int a[N], s[N];
// 寻找离散后的索引+1
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l <= r)
{
int mid = (l + r) >> 1;
// 离散后的索引+1
if (alls[mid] == x) return mid + 1;
else
{
if (alls[mid] < x) l = mid + 1;
else r = mid - 1;
}
}
return 0;
}
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());
for (auto item : add)
{
int x = find(item.first);
a[x] += item.second;
}
for (int i = 1; i <= alls.size(); i++) s[i] = s[i - 1] + a[i];
for (auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
十、区间合并
10.1、模板
// 将所有存在交集的区间合并
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;
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010;
int n;
vector<PII> segs;
void merge(vector<PII> &segs)
{
vector<PII> res;
// 初始化左右边界
int st = -2e9, ed = -2e9;
sort(segs.begin(), segs.end());
for (auto seg : segs)
{
if (ed < seg.first)
{
//不把最初的区间(st, ed)放进去
if (st != -2e9) res.push_back({st, ed});
st = seg.first;
ed = seg.second;
}
//更新右边界
else ed = max(ed, seg.second);
}
//防止输入的没有任何区间
//因为之前存储的都是前一个区间,所以最新的,最后一个独立的区间并没有被合并进去,所以要在出循环后在判断一次是否添加最后一个区间,如果这个区间不为空则添加进去
//本题中 n≥1,所以if可以去掉。但如果 n=0,去掉以后会有问题
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
int main()
{
cin >> n;
while (n--)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
merge(segs);
cout << segs.size() << endl;
return 0;
}