DAY6 位运算、离散化、区间和并

本文所有题目都可在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]。

  • 17
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值