基础算法(2022-02-13)
一.♥♥算法基础题目
1.基础算法
- AcWing 785. 快速排序
- AcWing 786. 第k个数
- AcWing 787. 归并排序
- AcWing 788. 逆序对的数量
- AcWing 789. 数的范围
- AcWing 790. 数的三次方根
- AcWing 791. 高精度加法
- AcWing 792. 高精度减法
- AcWing 793. 高精度乘法
- AcWing 794. 高精度除法
- AcWing 795. 前缀和
- AcWing 796. 子矩阵的和
- AcWing 797. 差分
- AcWing 798. 差分矩阵
- AcWing 799. 最长连续不重复子序列
- AcWing 800. 数组元素的目标和
- AcWing 801. 二进制中1的个数
- AcWing 802. 区间和
- AcWing 803. 区间合并
二.基础算法
1.排序
1.1 快速排序算法模板
题目:
给定你一个长度为n的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在1~10^9范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
题解:
快速排序-----分治思想 O(nlogn)
1.确定分界点q[l],q[l+r/2],q[r],随机一个都可以当作分界点
2.调整区间以x为分界,让<=x的在左边;>=x的在右边
解1:
2.1 两个数组a[],b[]
2.2 q[l--r] q[i]<=x x---->a[]
q[i]>=x x---->b[]
2.3 a[]放q[],b[]放q[]
解2:(双指针):
左边i,右边j;i左边所有的数都<=x,j右边数都是>=x
3.递归处理左右两段左右排序再拼一起
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n;
int q[N];
void quick_sort(int q[], int l, int r)
{
//只有一个数或者没有数的时候直接return
if (l>=r) return;
//边界的左右两边
//第一次的时候i和j都向两边移动所以i和j的初始值为i=l-1,j=r+1
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j)
{
do i ++; while(q[i]<x);
do j --; while(q[j]>x);
//如果指针没有相遇就交换两个数据
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j), quick_sort(q,j+1,r);
}
/*
//递归是j的话不能取到q[r]
void quick_sort(int q[],int l,int r){
if(l>=r) return;
int x=q[l],i=l-1,j=r+1;
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
//递归用i的话,边界不能取到q[l],会死循环 eg.1 2的时候
void quick_sort(int q[],int l,int r){
if(l>=r) return;
int x=q[(l+r+1)/2],i=l-1,j=r+1;
//int x=q[(l+r)/2],i=l-1,j=r+1;
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,i-1);
quick_sort(q,i,r);
}
*/
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
quick_sort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
return 0;
}
(1)例题1
(2)例题2快速选择
题目:
给定一个长度为n的整数数列,以及一个整数k,
请用快速选择算法求出数列的第k小的数是多少。
输入格式:
第一行包含两个整数 n和k。
第二行包含n个整数(所有整数均在1~109范围内),表示整数数列。
输出格式:
输出一个整数,表示数列的第k小数。
数据范围:
1≤n≤100000,1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
题解:
一、快排:O(NlogN)
快速排序-----分治思想 O(nlogn)
1.确定分界点q[l],q[l+r/2],q[r],随机
2.调整区间以x为分界,让Left<=x在左边;Right>=x在右边
3.递归处理左右两段左右排序再拼一起
二、快速选择:O(N)
1.k<=Sl,递归Left
2.k>Sl,递归Right,k-Sl
sl左边数的个数
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,k;
int q[N];
int quick_sort(int l,int r,int k){
if(l==r) return q[l];
int x=q[l],i=l-1,j=r+1;
while(i<j){
while(q[++i]<x);
while(q[--j]>x);
if(i<j) swap(q[i],q[j]);
}
//左边Left的数l----j----r
int sl=j-l+1;
if(k<=sl) return quick_sort(l,j,k);
//找第K个数,左边sl个数,右边k-sl个数
return quick_sort(j+1,r,k-sl);
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++) cin>>q[i];
cout<<quick_sort(0,n-1,k)<<endl;
}
1.2 归并排序算法模板
题目描述:
给定你一个长度为n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在1~10^9范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
题解:
归并排序---分治(稳定排序) O(nlogn)
1.以数组下标中间分开为左边和右边:mid=(l+r)/2
2.递归排序left,right
3.归并合二为一
双指针
----------
i 1 3 5 7 9
j 2 4 5 8 10
----------
比较a[i],b[j]大小,小的放进res[];
若a[i]<b[j]则a[i]放res[],i++;
若a[i]>b[j]则b[j]放res[],j++;
若a[i]==a[b],则两个任意一个都可以放进res[]
i先完后面把b[j]后面加res[];
j先完后面把a[i]后面加res[];
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n;
int a[N],temp[N]; //temp[]为辅助空间
void merge_sort(int a[],int l,int r){
//1个或者0个数
if(l>=r) return;
//(1)确定中间点
int mid=l+r>>1;
//(2)递归排序
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
//(3)归并
//k表示当前合并了几个数了,也就是temp里面有几个数了;i,j是两指针起点
int k=0,i=l,j=mid+1;
while(i<=mid && j<=r){
if(a[i]<a[j]) temp[k++]=a[i++];
else temp[k++]=a[j++];
}
//左边没有循环完
while(i<=mid) temp[k++]=a[i++];
//右边没有循环完
while(j<=r) temp[k++]=a[j++];
for(i=l,j=0;i<=r;i++,j++){
a[i]=temp[j];
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
merge_sort(a,0,n-1);
for(int i=0;i<n;i++) printf("%d ",a[i]);
return 0;
}
2.二分
2.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
题解:
二分的本质是边界二段性:
在某区间定义了某一种性质,一边满足该性质,另外一边不满足该性质,则二分能找到满足该性质的边界
一.区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:mid在右边,找下边界
l-----mid(true)----下边界,上边界---mid(false)-----r
左半边不满足性质 右半边满足性质
1.mid=l+r+1>>1 if(check(mid)) 1.1 true 要找的下边界在{mid,r}
包含mid(mid在整个左半边区间都能取到可能是下边界) 更新 l=mid; mid在下边界的左边
1.2 false 要找的边界在{l,mid-1}不包含mid
更新 r=mid-1
2.eg:当l=r-1,若mid=l+r>>1,则此时mid=l,会死循环
若mid=l+r+1>>1,则此时mid=r,正确
3.模板:找下边界(左边)--->l=mid
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:左边
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid; //l=mid的情况,中间点的更新方式是m=(l+r+1)/2
else r = mid - 1;
}
return l;
}
二.区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:mid在左边,找上边界
l-----mid(false)----下边界,上边界---mid(true)-----r
左半边不满足性质 右半边满足性质
1.mid=l+r>>1 if(check(mid)) 1.1 true 要找的上边界在{l,mid}包含mid
更新 r=mid ;mid在上边界的右边
1.2 false 要找的边界在{mid+1,r}不包含mid
更新 l=mid+1
2.模板:找上边界(右边)--->r=mid
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:右边
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // r=mid的情况,中间点的更新方式是m=(l+r)/2
else l = mid + 1;
}
return l;
}
需要写两个二分,一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数。
1.查找不小于x的第一个位置-->找上边界(右边)--->l---x---mid(true)----r
2.查找不大于x的最后一个位置-->找下边界(左边)
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
while(m--){
int x;
scanf("%d",&x);
int l=0,r=n-1;
//二分起始坐标
//相当于找x的上边界在右边
while(l<r){
int mid=l+r>>1;
//a[0]---x---mid(true)--a[l-1]如果满足的话check则mid在边界x(要找的数)的右边
//当a[mid] >= x时,说明mid及其左边可能含有值为x的元素
//当a[mid]小于x时,令l = mid + 1,mid及其左边的位置被排除了,可能出现解的位置是mid + 1及其后面的位置;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
//上面的while结束时候l==r
if(a[l]!=x) cout<<"-1 -1"<<endl;
else{
//相当于在左边
cout<<l<<" ";
int l=0,r=n-1;
while(l<r){
int mid=l+r+1>>1;
//a[0]---mid(true)---x--a[l-1]如果满足的话check则mid在边界x(要找的数)的左边
if(a[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
2.2 浮点数二分算法模板
求一个数的开平方根
题解:
1.直接迭代一百次
while(right - left > eps) { ... }
或者:
for(int i = 0; i < 100; i++) { ... }
2.求误差在某一个范围(比要求的经度值大2)
const double eps =1e-7; //精度eps
while(r - l> eps){
double mid = l+r>>1;
if (check(mid)) r = mid; //判定,然后继续二分
else l = mid;
}
或者:
for(int i = 0; i < 100; i++) {
double mid = l+r>>1;
if (check(mid)) r = mid; //判定,然后继续二分
else l = mid;
}
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
double x;
cin>>x;
double l=0,r=x;
//比要求的经度值大2
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid>=x) r=mid;
else l=mid;
}
/*
或者直接迭代一百次
for(int i=0;i<100;i++){
double mid=(l+r)/2;
if(mid*mid>=x) r=mid;
else l=mid;
}*/
printf("%lf\n",l);
return 0;
}
ps:推荐博客1 推荐博客2
3.高精度
3.1 几种常见的情况:
(1)A+B 位数1e6
(2)A-B 位数1e6
(3)A*b len(A)<=1e6 ,b<=1e9
(4)A/B
3.2 步骤:
(1)大整数存储(逆序存储)
(2)计算
3.3 高精度加法
题目:
现在有一个简单的问题,给你两个正整数A和B,你需要计算出A+B 的结果。不过要注意哦,这两个正整数非常大。
输入描述:
输入两个正整数A和 B ,A和 B的位数不超过 1000000。
输出描述:
输出 A+B,结果占一行。
数据范围:
1<=参数长度<=1e6;
题解:
和我们笔算加法时一样的思路,注意数据存储的时候时倒着存储的
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
//加引用加快效率,不加引用的话把整个数组copy一遍,加了不用拷贝;数字存个位到0
vector<int> add(vector<int> &a,vector<int> &b){
vector<int> c;
//if(a.size()<b.size()) return add(b,a);
int t=0; //表示进位,一开始t为0个位不需要进位
for(int i=0;i<a.size()||i<b.size();i++){
if(i<a.size()) t+=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%10);
t/=10;
}
//最后最高位还有没有进位,有的话加上1
if(t) c.push_back(1);
return c;
}
int main(){
string str1,str2;
vector<int> a,b;
cin>>str1>>str2;
//存储进去的是数字
for(int i=str1.size()-1;i>=0;i--) a.push_back(str1[i]-'0'); //a={6,5,4,3,2,1}
for(int i=str2.size()-1;i>=0;i--) b.push_back(str2[i]-'0');
auto c=add(a,b);
for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
return 0;
}
3.4 高精度减法
题目:
现在有一个简单的问题,给你两个正整数A和B,你需要计算出A-B 的结果。不过要注意哦,这两个正整数非常大。
输入描述:
输入两个正整数A和 B ,A和 B的位数不超过 1000000。
输出描述:
输出 A-B,结果占一行。
数据范围:
1<=参数长度<=1e6;
题解:
A3 A2 A1 A0
- B2 B1 B0
----------------
if A>=B
A-B
else
-(B-A)
----------------
Ai-Bi-t (1)当>=0 Ai-Bi-t
(2)当<0 Ai-Bi+10-t
t为借为
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
//判断a>=b
bool cmp(vector<int> &a,vector<int> &b){
//位数不同的,位数大的数就大,位数小的数就小
if(a.size()!=b.size()) return a.size()>b.size();
//位数相同时,需要从大到小
// for(int i=0;i<A.size();i++){
// if(a[i]!=b[i])
// return a[i]>b[i];
// }
for(int i=a.size()-1;i>=0;i--){
if(a[i]!=b[i])
return a[i]>b[i];
}
return true;
}
// C=A-B
vector<int> sub(vector<int> &a,vector<int> &b){
vector<int> c;
for(int i=0,t=0;i<a.size();i++){
t=a[i]-t;
//判断Bi是否越界,否则借位
if(i<b.size()) t-=b[i];
/*
(t+10)%10
(1)t>=0 t本身
(2)t<0 返回t+10
*/
c.push_back((t+10)%10);
//t<0表示Ai不够减需要借位t=1,不需要借位的话t=0
if(t<0) t=1;
else t=0;
}
//去掉前导0
while(c.size()>1 && c.back()==0) c.pop_back();
return c;
}
int main(){
string str1,str2;
cin>>str1>>str2;
vector<int> a,b;
for(int i=str1.size()-1;i>=0;i--) a.push_back(str1[i]-'0');
for(int i=str2.size()-1;i>=0;i--) b.push_back(str2[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;
}
3.5 高精度乘低精度
题目描述:
给定两个正整数A,B,请你计算A*B
输入格式:
共两行,第一行包含正整数A,第二行包含正整数B;
输出格式:
共一行,包含A*B的值;
数据范围:
1<=A长度<=1e6
1<=B<=1e4
题解:
1 2 3 A
* 1 2 B
--------------
C3 C2 C1 C0
1 4 7 6
--------------
C0=(3*12)%10=6
T1=3*12/10=3
C1=(2*12+T1)%10=7
T2=2
C2=(1*12+T2)%10=4
T3=1
C3=1
注意:将B看作一个整体
代码:
#include <bits/stdc++.h>
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
t/=10; //进位t/10
}
return c;
}
int main(){
string a;
int b;
cin>>a>>b;
vector<int> A;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
auto c=mul(A,b);
for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
return 0;
}
3.6 高精度除以低精度
题目描述
现在给你一个简单的问题,给你两个正整数A和B,你需要计算出A除以B的结果。
不过要注意哦,这里的A非常大,b小。大到老师提醒你们需要使用高精度来做。
输入描述
输入两个正整数A和B,A的位数不超过10000,B的位数不超过100位。
输出描述
输出A除以B的结果,只保留整数部分,结果占一行。
题解:
为了统一数据存储,当运算时可能加减乘除都有,所以将除法也逆序存储
代码:
#include <bits/stdc++.h>
using namespace std;
//A/b 商是c,余数是r
vector<int> div(vector<int> &A,int b,int &r){ //r是引用
vector<int> c; //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;
vector<int> A;
cin>>a>>b;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0'); //记得-'0'
//r为余数
int r;
auto c=div(A,b,r);
for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
cout<<endl<<r<<endl;
return 0;
}
4.前缀和与差分
4.1 一维前缀和
题目描述:
有长度为n的整数序列,接下来再输入m次询问,每次询问输入一对l,r
对于每个询问,输出序列中从第1个数到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
题解:
a1 a2 a3 a4 a5 ...an
前缀和:Si=a1+a2+a3+..+ai
数组下标从1开始,Si要从1开始,主要是为了处理边界,比如求解{1,10}则S[10]-S[0]
1.S[i]怎么求 先定义:S[0]=0,后求S[i]=S[i-1]+a[i]
2.S[i]作用 求[l,r]和:S[r]-S[l-1]
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int a[N],s[N];
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
while(m--){
int l,r;
cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
4.2 二维前缀和
题目描述
JM有一个一个n行m列的整数矩阵.
好奇的JM有q次询问,每个询问包含四个整数x_1, y_1, x_2, y_2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入描述
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数a{ij},表示整数矩阵。
接下来q行,每行包含四个整数x_1, y_1, x_2, y_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 <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n,m,q;
int a[N][N],s[N][N];
int main(){
// ios::sync_with_stdio(false);
scanf("%d%d%d",&n,&m,&q);
// cin>>n>>m>>q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
// cin>>a[i][j];
scanf("%d",&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,y1,x2,y2;
// cin>>x1>>y1>>x2>>y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
//cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl; //算子矩阵
}
return 0;
}
4.3 一维差分
题目:
输入一个长度为n的整数序列
接下来输入m个操作,每个操纵包含三个整数l,r,c,表示将序列中的[l,r]之间的每个数加上c.
请你输出进行完所有操作后的序列。
输入格式:
第一行包含两个整数n和m
第二行包含n个整数,表示整数序列
接下来m行,每行包含三个整数l,r,c,表示一个操作
输出格式:
共一行,包含n个整数,表示的是序列
数据范围:
1<=n,m<=100000
1<=l<=r<=n
-1000<=c<=1000
-1000<=整数序列中元素的值<=1000
输入数据:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出数据:
3 4 5 3 4 2
题解:
b[i]称为a[i]的差分,a[i]为b[i]的前缀和
a[1] a[2] a[3] ..a[n]
构造b[1] b[2] b[3] ..b[n]使得a[i]=b[1]+b[2]+...+b[i]
b[1]=a[1]
b[2]=a[2]-a[1]
b[3]=a[3]-a[2]
.....
b[n]=a[n]-a[n-1]
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int n,m;
int a[N],b[N];
void insert(int l,int r ,int c){
b[l]+=c;
b[r+1]-=c;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) insert(i,i,a[i]);
while(m--){
int l,r,c;
scanf("%d%d%d",&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++) printf("%d ",b[i]);
return 0;
}
4.4 二维差分
题目:
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1,y1,x2,y2,c,
其中(x1,y1)和(x2,y2)表示一个子矩阵的左上角坐标和右下角坐标
每个操作都要将选中的子矩阵中的每个元素的值都加上c
请您将进行完所有操作的矩阵输出
输入格式:
第一行包含整数n,m,q
接下来n行,每行包含m个整数,表示整数矩阵
接下来q行,每行包含5个整数x1,y1,x2,y2,c,表示一个操作
输出格式:
共n行,每行m个整数,表示所有操作进行完毕后的最终矩阵
数据范围:
1<=n,m<=1000
1<=q<=100000
1<=x1<=x2<=n
1<=y1<=y2<=m
输入数据:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出数据:
2 3 4 1
4 3 4 1
2 2 2 2
题解:
原矩阵a[i][j]
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1][y1] += c,
S[x2+1][y1] -= c,
S[x1][y2+1] -= c,
S[x2+1][y2+1] += c
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],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(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&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,y1,x2,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-1][j]+b[i][j-1]-b[i-1][j-1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%d",b[i][j]);
}
puts("");
}
return 0;
}
5.双指针算法
5.1 双指针算法
题目:
输入字符串,将字符串以空格分开,每行一个字符
题解:
for(int i=0,j=0;i<n;i++){
if(j<n && check(j)) j++;
//题目逻辑思路
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
O(n^2)
运用双指针算法将朴素算法时间复杂度降为O(n)
代码:
#include <iostream>
#include <string.h>
using namespace std;
int main(){
char str[100];
gets(str);
int len=strlen(str);
for(int i=0;i<len;i++){
int j=i;
while(j<len && str[j] !=' ') j++;
//这道题目的具体逻辑
for(int k=i;k<j;k++) cout<<str[k];
cout<<endl;
//更新i
i=j;
}
return 0;
}
题目:
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度
输入格式:
第一行包含整数n
第二行包含n个整数,表示最长的不包含重复数字的连续子序列的长度
数据范围:
1<=n<=100000
输入样例:
5
1 2 2 3 4
输出样例:
3
题解:
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
//朴素做法:O(n^2)
for(int i-0;i<n;i++){
for(int j=0;j<=i;j++){
if(check(i,j)){
res=max(res,i-j+1);
}
}
}
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N],s[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int res=0;
//j表示往左边离i最远到什么地方,j------i
//当i向后面走的时候,j不可能向前面走
for(int i=0,j=0;i<n;i++){
s[a[i]]++;
//新加入的数有重复的话s[a[i]]>1
while(s[a[i]]>1){
s[a[j]]--;
j++;
}
res=max(res,i-j+1);
}
cout<<res<<endl;
return 0;
}
6.位运算
6.1 位运算
n的二进制表示中第k位是几
题解:
n=15=(1 1 1 1)2
3 2 1 0
1.先把第k位移动到最后一位 n>>k
2.看个位(最后一位)是几 x&1
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n=10; //1010
//倒着遍历
for(int i=3;i>=0;i--) cout<<(n>>i & 1);
return 0;
}
题目:
给定一个长度为n的数列,求出数列中每个数的二进制表示中1的个数
输入:
第一行包含整数n
第二行包含n个整数,表示整数个数列
输出:
共一行,包含N个整数,其中第i个数表示数列中第i个数的二进制中1的个数
数据范围:
1<=n<=1e5
0<=元素值<=1e5
输入:
5
1 2 3 4 5
输出:
1 1 2 1 2
题解:
lowbit(x):返回x的最后(最右边)一个1
x&-x=x&(~x+1) //求补码取反+1
-x=~x+1 补码
x=10101010 1 00000
~x=01010101 0 11111
~x+1=01010101 1 00000
x&(~x+1)=00000000 1 00000
eg:
x=1010 lowbit(x)=10
x=101000 lowbit(x)=1000
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int lowbit(int x){
return x&-x;
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
while(n--){
int x;
cin>>x;
int res=0;
while(x) x-=lowbit(x),res++; //每次减去x的最后一位
cout<<res<<" ";
}
return 0;
}
7.整数离散化
7.1 离散化
假定有一个无限长的数轴,数轴上每个坐标上的数据都是0
现在,我们首先进行n次操作,每次操作将某一位置x上的数加c
接下来,进行m次询问,每个询问包含两个整数l和r,你需要求出在区间[l,r]之间的所有数的和
输入格式:
每一行包含两个整数n和m
接下来n行,每行包含两个整数x和c
再接下来m行,每行包含两个整数l和r
输出:
共m行,每行输出一个询问中所求的区间内数字和
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
题解:
离散化:
a[] 1 3 100 2000 50000
0 1 2 3 4
(1)a[]中可能有重复元素-->去重
unique去重:将数组中所有元素去重,并且返回去重之后元素的尾端点,在erase
(2)如何算出a[]离散化后的值-->二分
找到第一个>=x的位置
(3)代码
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
}
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N=3e5+10;
int n,m;
int a[N],s[N]; //s[]前缀和
vector<int> alls; //存的所有离散化得结果
typedef pair<int,int> PII;
vector<PII> add,query; //插入操作,求操作
int find(int x){
int l=0,r=alls.size()-1;
while(l<r){
int mid=r+l>>1;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
//映射为1,2,3,4...
return r+1;
}
//自己定义unique 原理(1)它是第一个数 (2)a[i]!=a[i-1]
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;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=0;i<n;i++){
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x); //x加到离散化数组里面
}
for(int i=0;i<m;i++){
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.erase(unique(alls),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;
}
8.区间合并
8.1 区间合并
题目:
给定n个区间[l,r];要求合并所有有交集的区间
注意如果在端点处相交,也算有交集
输出合并完成后的区间个数
如:[1,3]和[2,6]可以合并为一个区间[1,6]
输入格式:
第一行包含整数n
接下来n行,每行包含两个整数l和r
输出格式:
共一行,包含一个整数,表示合并完成后的区间个数
输入:
5
1 2
2 4
5 6
7 8
7 9
输出:
3
数据范围:
1≤n≤100000,
−10^9≤l_i≤r_i≤10^9
题解:
1.按照区间左端点排序
2.扫描strt---end,将所有可能的区间合并
代码:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10;
int n;
vector<PII> a;
void merge(vector<PII> &a){
vector<PII> res;
sort(a.begin(),a.end());
int st=-2e9,ed=-2e9; //设置边界值
for(auto it:a){
if(ed<it.first){
if(st!=-2e9) res.push_back({st,ed});
st=it.first,ed=it.second;
}else{
ed=max(ed,it.second);
}
}
if(st!=-2e9) res.push_back({st,ed}); //防止输入是空
a=res;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
a.push_back({l,r});
}
merge(a);
cout<<a.size()<<endl;
return 0;
}