本文所有题目都可在acwing题库中找到,本文仅进行归纳整理
题目:acwing801
给定一个长度为 n的数列,请你求出数列中每个数的二进制表示中 1的个数。
输入格式
第一行包含整数 n。
第二行包含 n个整数,表示整个数列。
输出格式
共一行,包含 n个整数,其中的第 i个数表示数列中的第 i个数的二进制表示中 1的个数。
数据范围
1≤n≤100000
0≤数列中元素的值≤10^9
这道题当然可以从大到小2^n不断地遍历,然后把结果1/0存入数组,再遍历数组得到答案。
但是过于低效,本处采用位运算(与运算),将两个数的补码进行比较,只有都为1才为,否则为0。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int lowbit(int x) {
return x & (-x);
}
int main() {
int n;
cin >> n;
while (n--) {
int x; int res = 0;
cin >> x;
while (x) {
x -= lowbit(x);
res++;
}
cout << res << " ";
}
}
本处代码中lowbit函数将x与(-x)进行位运算,可以得到源码中最低位的1,然后减去最低位的1(用res进行记录),不断重复就可以得到答案。
想要了解更多关于位运算的知识可以参考位运算:按位与、按位或、按位异或、按位左移、按位右移-CSDN博客
题目:acwing802
假定有一个无限长的数轴,数轴上每个坐标上的数都是 00。
现在,我们首先进行 n次操作,每次操作将某一位置 x上的数加 c。
接下来,进行 m次询问,每个询问包含两个整数 l和 r,你需要求出在区间 [l,r]之间的所有数的和。
输入格式
第一行包含两个整数 n和 m。
接下来 n行,每行包含两个整数 x和 c。
再接下来 m行,每行包含两个整数 l和 r。
输出格式
共 m行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9≤x≤10^9
1≤n,m≤10^5
−10^9≤l≤r≤10^9
−10000≤c≤10000
初步审题,其实会觉得和前文讲述的前缀和一致,但是细看之下其实还是有差异的。
本文中数据范围是−10^9≤x≤10^9,范围很庞大,但是实际上运用到的范围其实只占很小的一部分。我们可以将分散在数轴上的点聚合起来,采用离散化的思想(将多而稀的区间投影到小而密的区间)。
#include<iostream>
#include<vector>
#include<algorithm>
//离散区间和
using namespace std;
const int N = 1e6 + 10;
int a[N], s[N];
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 = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}//二分法查找
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
int x, c;//实际上的点坐标和加值
cin >> x >> c;
add.push_back({ x,c });//add存入(点,值)
alls.push_back(x);//alls存入值
}
for (int i = 0; i < m; i++) {
int l, r;//区间的始末位置
cin >> l >> r;
query.push_back({ l, r });//query存入区间的始末位置(坐标)
alls.push_back(l);//alls存入区间始末位置
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;
}
本处代码中,find()函数就是一个简单的二分法查找位置。用add中存入输入的点和值的点集,query存入输入区间的始末位置,alls存入输入的值和区间的始末位置。
在alls数组中和我们进行去重和排序。遍历add和query数组,采用前缀和的思想得到我们想要的答案。
alls数组去重排序的好处是提高遍历的效率,同时避免二分法查找出错(二分法查找必须包含某种性质,比如单调性)。
题目:acwing803
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。
输入格式
第一行包含整数 n。
接下来 n行,每行包含两个整数 l和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000
−10^9≤li≤ri≤10^9
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n, cnt = 0;
struct s {
int l, r;
}a[100010],ans[100010];
int cmp(s a, s b) {
if (a.l == b.l) return a.r < b.r;
return a.l < b.l;
}
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].l >> a[i].r;
}
sort(a + 1, a + 1 + n, cmp);
ans[++cnt] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i].l <= ans[cnt].r) ans[cnt].r = max(ans[cnt].r, a[i].r);
else ans[++cnt] = a[i];
}
cout << cnt << endl;
return 0;
}
本题我找到了一种更加简单的算法,采用了结构体。
对数组中的元素用cmp函数进行排序,如果左端点相同就比较右端点大小,反之就比较左端点。
将排完序后的第一位存入ans,再进行比较迭代。排序的目的是为了使得a的区间范围比b更小或者更前。方便后面的筛查。如果a[i]的左端点比ans的右端点更小,那么更新ans的右端点;否则就存入a[i]。