目录:
786. 第k个数
787. 归并排序
789. 数的范围
797. 差分
双指针算法:
799. 最长连续不重复子序列
800. 数组元素的目标和
位运算:
801. 二进制中1的个数
离散化:
802. 区间和
786. 第k个数
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数
输入格式
第一行包含两个整数 n和 k。
第二行包含 n 个整数(所有整数均在10^9范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k 小数。
输入样例:
5 3
2 4 1 5 3
输出样例:
3
学习了STL中的sort就应该实践一下
#include
using namespace std;
const int N = 1e5 + 10;
int n, m;
int q[N];
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++) cin >> q[i];
sort(q, q + n);
cout << q[m - 1] << endl;
return 0;
}
快排:
#include
using namespace std;
const int N = 1e5 + 10;
int n, m;
int q[N];
void quick_sort(int q[], int l, int r)
{
if(l >= r) return;
int x = q[l + r >> 1], 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);
}//这种方法好记很多
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++) cin >> q[i];
quick_sort(q, 0, n - 1);
cout << q[m - 1] << endl;
return 0;
}
787. 归并排序
给定你一个长度为 n 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含n 个整数(所有整数均在1~10^9范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
#include
using namespace std;
const int N = 100500;
int a[N],tmp[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]) tmp[k++] = q[i++];
else tmp[k++] = q[j++];
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
}
//板子,记下就好
int main()
{
int n;
cin>>n;
for(int i = 0;i < n;i++)cin>>a[i];
merge_sort(a,0,n-1);
for(int i = 0;i < n;i++)cout< return 0;
}
求解逆序对问题,实际上有三种算法可以处理,分别是冒泡算法,归并排序,以及树状数组求解.
这里显然我们可以用性价比最高,代码最好写,效率特高的归并排序算法。
我们要注意一点,就是当我们发现填充第二个数组中的数,加入备用数组的使用,都要统计mid−i+1,因为此时此刻,我们第一个数组中剩余的所有数,都会和它构成逆序对。
去网站上去看数据范围,容易爆,于是我们开long long
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], tmp[N];
LL merge_sort(int q[], int l, int r)
{
if (l >= r) return 0;
int mid = l + r >> 1;
LL res = 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]) tmp[k ++ ] = q[i ++ ];
else
{
res += mid - i + 1;
tmp[k ++ ] = q[j ++ ];
}
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
return res;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
cout << merge_sort(a, 0, n - 1) << endl;
return 0;
}
789. 数的范围
本题是练习二分很好的一道题目,二分程序虽然简单,但是如果写之前不考虑好想要查找的是什么,十有八九会是死循环或者查找错误,就算侥幸写对了也只是运气好而已。用二分去查找元素要求数组的有序性或者拥有类似于有序的性质,对本题而言,一个包含重复元素的有序序列,要求输出某元素出现的起始位置和终止位置,翻译一下就是:在数组中查找某元素,找不到就输出-1,找到了就输出不小于该元素的最小位置和不大于该元素的最大位置。所以,需要写两个二分,一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数。查找不小于x的第一个位置
#include
using namespace std;
const int N = 100001;
int n,m;
int q[N];
int main()
{
cin>>n>>m;
for(int i = 0;i < n;i++)cin>>q[i];
while(m--)
{
int x;
cin>>x;
int l = 0,r = n-1;
while(l < r)
{
int mid = l+r>>1;
if(q[mid] >= x)r = mid;
else l = mid + 1;
}
if(q[l] != x)cout<<"-1 -1"<
{
cout<
int l = 0,r = n - 1;
while(l < r)
{
int mid = l+r+1>>1;
if(q[mid] <= x)l = mid;
else r = mid - 1;
}
cout<
}
return 0;
}
AcWing 795. 前缀和
输入一个长度为 n的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
第一种:
#include
using namespace std;
const int N = 100010;
int a[N],s[N];
int main()
{
int n,m;
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;
scanf("%d%d",&l,&r);
printf("%d\n",s[r] - s[l - 1]);
}
return 0;
}
第二种:
#include
using namespace std;
const int N = 100010;
int a[N],s[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 0;i < n;i++) cin>>a[i];
for(int i = 1;i <= n;i++) s[i] = s[i-1] + a[i-1];
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r] - s[l - 1]);
}
return 0;
}
797. 差分
输入一个长度为n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r]之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
#include
using namespace std;
const int N = 100010;
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;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)cin>>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;
}
799. 最长连续不重复子序列
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数n 。
第二行包含 n 个整数(均在 0∼10^5 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
输入样例:
5
1 2 2 3 5
输出样例:
3
#include
using namespace std;
const int N = 1e6+10;
int a[N],s[N];
int n;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)scanf("%d",&a[i]);
int res = 0;
for(int i = 0,j = 0;i < n;i++)
{
s[a[i]]++;//向右扩展区间右端点
//当扩展完区间右端点之后,有可能这个元素q[i]会有重复,下面这个while循环就是用来去除重复
//去重只有一个办法,就是收缩区间左端点,同时收缩时要保证j是小于i的
while(j < i && s[a[i]] > 1)s[a[j++]]--;//区间数量减一,之后区间端点右移
res = max(res,i-j+1);//完成上述扩展和收缩之后,该区间是一个满足要求的区间,记录一下长度
}
cout<
}
800. 数组元素的目标和
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i 和 j。
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
算法1
(双指针) O(n)
i从 0开始 从前往后遍历
j从 m - 1开始 从后向前遍历
和纯暴力的O(n^2)算法的区别就在于j指针不会回退
#include
using namespace std;
const int N = 1e6+10;
int a[N],b[N];
int count = 0;
int n,m,x;
int main()
{
scanf("%d%d%d",&n,&m,&x);
for(int i = 0;i < n;i++)scanf("%d",&a[i]);
for(int j = 0;j < m;j++)scanf("%d",&b[j]);
for(int i = 0,j = m - 1;i < n;i++)
{
while(j >= 0 && a[i]+b[j] > x)j--;
if(j >= 0 && a[i]+b[j] == x)cout<
}
return 0;
}
算法1
(lowbit) O(nlogn)
使用lowbit操作,进行,每次lowbit操作截取一个数字最后一个1后面的所有位,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数,
lowbit原理
根据计算机负数表示的特点,如一个数字原码是10001000,他的负数表示形势是补码,就是反码+1,反码是01110111,加一则是01111000,二者按位与得到了1000,就是我们想要的lowbit操作
801. 二进制中1的个数
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n个整数,表示整个数列。
输出格式
共一行,包含 n个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
#include
using namespace std;
const int N = 100010;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int x;
cin>>x;
int res = 0;
while(x) x -= lowbit(x),res++;
printf("%d ",res);
}
return 0;
}
802. 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 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
#include
#include
#include
using namespace std;
int n,m;
typedef pair
vector
vector
const int N = 300010;//n+2m的数据(两个操作,分别需要一个数和一对数的数据范围)
int a[N],s[N];
//因为这个数组是有序的,所以我们用二分
int find(int 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 l+1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++)
{
int x,c;
scanf("%d%d",&x,&c);
add.push_back({x,c});
alls.push_back(x);
}
for(int i = 0;i < m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
//对已经映射到alls数组里面的数进行排序
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< }
return 0;
}