文章目录
- 1. 位运算
- 2. 双指针算法
- 3.离散化
- 4.区间合并
给定一个数字n
,求n
的二进制表示中第k
位数字。
思路:比如n = 15
,n
的二进制可表示为1111
1. 先把第k
位移到最后一位:n >> k
(n从第0位开始)
2. 然后根据个位的数字来确定第k
位的数字:x & 1
将以上两步合并,就是n >> k & 1
位运算模版
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
模板题1
输入一个正整数n = 10,将n的二进制表示输出。
输出格式
共一行,包含n的二进制表示。
#include
using namespace std;
int main() {
int n = 10;
for (int k = 3; k >= 0; k--) cout << (n >> k & 1);
return 0;
}
模板题2 AcWing 801. 二进制中1的个数
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
数据范围
1≤n≤100000,
0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
#include
using namespace std;
// 返回x的最后一位1
int lowbit(int x) {
return x & (-x); //相当于 x & (~x + 1)
}
int main() {
int n;
scanf("%d", &n);
while (n--) {
int x, res = 0;
scanf("%d", &x);
while (x) {
x -= lowbit(x);
res++;
}
printf("%d ", res);
}
return 0;
}
若使用朴素做法的时间复杂度为:O(n2),那么使用双指针算法,可将时间复杂度降为:O(n)。
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
双指针算法模版
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
模板题1 AcWing 799. 最长连续不重复子序列
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤整数长度≤100000
输入样例:
5
1 2 2 3 5
输出样例:
3
#include
using namespace std;
const int N = 100010;
int n, q[N], cnt[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &q[i]);
int res = 0;
for (int i = 0, j = 0; i < n; i ++) {
cnt[q[i]]++;
while (cnt[q[i]] > 1 && j < i) {
cnt[q[j]]--;
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
模板题2 AcWing 800. 数组元素的目标和
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i 和 j。
数据范围
数组长度不超过105。
同一数组内元素各不相同。
1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
#include
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];
int main()
{
scanf("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
for (int i = 0; i < m; i ++ ) scanf("%d", &b[i]);
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 << i << ' ' << j << endl;
}
return 0;
}
有许多数分布在数轴上,数的取值范围很广,但是这些数的个数相对来说不是很多,要求对数轴上某个区间上的数进行求和等操作,可使用离散化模版,将数值映射到下标。
离散化模版
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
}
模板题 AcWing 802. 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−109≤x≤109,
1≤n,m≤105,
−109≤l≤r≤109,
−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
#include
#include
#include
using namespace std;
const int N = 300010; //n次插入和m次查询相关数据量的上界
typedef pair<int, int> PII;
vector<PII> add, query; //存储插入和询问操作的数据
vector<int> alls; //存储所有待离散化的值(所有与插入和查询有关的),将待离散化的值映射到alls的下标
int a[N], s[N]; // a[N]用来表示离散化后的数组,s[N]用来表示前缀和
int n, m;
//二分求出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;
}
int main() {
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;
}
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
思路:可以将n个区间先按左端点排序,维护一个区间,并将维护区间与后面一个区间进行三种情况的比较(1.后一个区间包含于维护区间;2.后一个区间与维护区间部分相交;3.两个区间不相交),若后一个区间和当前维护的区间不相交时,需要将当前维护的区间存储到结果数组里,并将后一个区间作为新的维护区间,继续以上有关操作。
区间合并模版
// 将所有存在交集的区间合并
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;
}
模板题 AcWing 803. 区间合并
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000,
−109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
#include
#include
#include
using namespace std;
typedef pair<int, int> PII;
vector<PII> segs;
// 将所有存在交集的区间合并
void merge(vector<PII> &segs) {
vector<PII> res;
sort(segs.begin(), segs.end()); //按左端点排序
int st = -2e9, ed = -2e9; //初始化st和ed,ed代表维护区间的结尾,st代表维护的区间开头
for (auto item : segs) {
if (ed < item.first) { //两个区间无法合并的情况
if (ed != -2e9) res.push_back({st, ed}); //把维护的区间放进res数组
st = item.first, ed = item.second; //更新维护的区间
}
else //两个区间可以合并的情况
ed = max(ed, item.second);
}
if (ed != -2e9) res.push_back({st, ed}); //将最后一个维护区间放进res数组中
segs = res;
}
int main() {
int n;
cin >> n;
while (n--) {
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
merge(segs);
cout << segs.size() << endl;
return 0;
}
以上模版和笔记均来源:AcWing的算法基础课
链接:https://www.acwing.com/blog/content/277/