G.Greater and Greater - 2020牛客暑期多校训练营(第二场) (位运算,二分查找)
G.Greater and Greater - 2020牛客暑期多校训练营(第二场) (位运算,二分查找)
题目下载:https://files.cnblogs.com/files/popodynasty/G.Greater_and_Greater.pdf.zip
题目大意
给定两个数列A、B,其中数列A的长度一定不小于数列B的长度。求数列A中有多少这样的连续子数列,其数列长度和B数列相同,且子数列中的各个元素均不小于B数列中对应位置的元素。
题解
思路
这题无法避免将A中的各个元素与B中的各个元素比较大小。
我们可以对A中的每一个元素\(A_i\)求一个长度为m的bitset \(S_i\),其中\(S_i[j] = 1 \iff A_i \geq B_j\)
如样例
A: 1 4 2 8 5 7
B: 2 3 3
我们得到的bitset就是:
0 0 0
/ 1 1 1
/ 1 0 0
/ 1 1 1
/ 1 1 1
/ 1 1 1
将这些bitset这样排列,每一个长度为3的列全为1,则对应一个有效答案。
思考:
- 为什么要交错排列?
- 为什么长度为3的列全都是1就对应着有效答案?
- 为什么要将第一个元素(1)的bitset放右下方,将最后一个元素(7)的bitset放最左上方,如果用相反的顺序有什么逻辑错误?
接下来遇到的问题是,如果直接用for循环比较,复杂度为O( n*m ).需要换一种更高效的方法来比较。
看样例数据:
A: 1 4 2 8 5 7
B: 2 3 3
符合题意的子数列为2 8 5
和 8 5 7
其实样例数据等价于:
A: 1 3 2 3 3 3
B: 2 3 3
符合题意的子数列为 2 3 3
和 3 3 3
可以发现的是,对A数列中的每一个元素\(\alpha\),我们都可以把它缩小成B数列中的一个元素\(\beta\),这个元素\(\beta\)是数列B中最接近\(\alpha\)且不超过\(\alpha\)的元素。(如果元素\(\alpha\)比B数列的每一个元素都要小,那么保持不变即可)。这样做不会影响原答案(而且找到的每一个子数列的位置也不变)
这样做的意义在于,可以将O(m\*n
)的比较过程,转化为O(n\* logm
) 为什么是log,先往下看后面会讲
如此一来,我们只需要将B中的所有元素与B数列求一个大小关系即可。
我们将B数列排序(排序的时候还要标记原位置)
#define MaxB 20
typedef pair<int,int> pii;
int temp;
int m; // B数列长度
vector<int> B(1); // B数列,实现加一个占位
vector<pii> sorted_B;
// vector 用于存储排序后的B数列,用二元组的意义:first存储数值,second存储其在原B数列的位置
sorted_B.push_back(make_pair(0,0));
// 加入一个(0,0)既可以占位,又可以提供一个最小值方便之后二分查找
cin >> m;
for(int i = 1; i <= m; i++){
cin >> temp;
B.push_back(temp);
sorted_B.push_back(make_pair(temp,i));
}
sort(sorted_B.begin(),sorted_B.end()); // 默认按偏序的字典序排序
然后求出对排序后的B数组求对应的bitset
排序的意义:
- 方便之后求A数列的bitset时做二分查找
- 排序后的B数组相邻的元素的bitset存在递推关系
如何得到的这个递推关系?
举个例子
B: 2 3 4 5 4
sorted_B: 2 3 4 4 5
假如我刚刚求完了元素3
的bitset,为 1 1 0 0 0
,现在要求元素4
的bitset,由于4
是3
的相邻元素且更大,所有3
的bitset为1的位,在4
的bitset中也为1;在此基础上,4
的bitset在所有B数值为4的位置也要设置为1.
因此有
for (int i = 1; i <= m; ) {
B_bitset[i] = B_bitset[i-1];
int j = i;
while (j <= m) {
if(sorted_B[i].first == sorted_B[j].first){
B_bitset[i].set(sorted_B[j].second);
j++;
}else break;
}
后面所有的4
的bitset全部都和现在求出的这个4
的bitset相同。
因此有
for (int k = i+1; k < j; k++) {
B_bitset[k] = B_bitset[i];
}
bitset<MaxB> B_bitset
void get_B_bitset(){
// 这个函数能够得到标准的bitset,但是没有必要;可以再二分查找上做文章.
// 这里还是写一个得到标准bitset的函数,略微慢一些。如果想要快一点,可以看文末参考博客的代码
for (int i = 1; i <= m; ) {
B_bitset[i] = B_bitset[i-1];
int j = i;
while (j <= m) {
if(sorted_B[i].first == sorted_B[j].first){
B_bitset[i].set(sorted_B[j].second);
j++;
}else break;
}
for (int k = i+1; k < j; k++) {
B_bitset[k] = B_bitset[i];
}
i = j;
}
}
现在我们已经得到了sorted_B的bitset,利用它我们可以得到A的bitset。方法就是利用二分。
(一些常见的二分模板: https://zhuanlan.zhihu.com/p/42185010)
假如 B 是 3 5 7 ,如果要求 A 有一位的值为 6 ,那么 6 所对应的 bitset 的值和 B 中 5 的 bitset的值一样 (均为110),这样就能 log 得到 A 的bitset值。
int find_pos(int val){ // 在 sorted_B 中找到最接近val的、<= val的元素(如果出现val比所有B中元素都小,搜索到0)
int head = 0,tail = m;
while (head < tail) {
int mid = (head + tail) / 2;
if(sorted_B[mid].first > val){
tail = mid - 1;
}else if(sorted_B[mid].first < val){
head = mid + 1;
}else{
head = mid;
break;
}
}
while (head > 0) {
if(sorted_B[head].first > val)
head--;
else break;
}
return sorted_B[head].second;
}
现在我们已经可以O(logm)求出A中任意元素的bitset \(S_i\)了,但不需要全部求出来并存起来(可能也存不下),只需要滚动即可。
- 在解决了\(S_i\)后要考虑如何得到答案,我们发现\(S_i\)和\(S_{i-1}\)相比,只需要将\(S_{i-1}\)右移一位再进行
与
操作,就能把两者相联系起来,那么我们只要维护一个从起始开始的与
的和 A_bitset,如果 A_bitset[1]=1那么就对答案有贡献。- 注意要先处理好 A_bitset 中后m−1 项,而且在 A_bitset 的每次右移过程中需要将 A_bitset[m]赋值为 1。不然会影响到之后的判断。
bitset<MaxB> A_bitset;
int ans = 0;
for (int i = n; i >= n - m + 1; i--) {
A_bitset >>= 1;
A_bitset[m] = 1;
A_bitset &= B_bitset[find_pos(A[i])];
}
for (int i = n - m + 1; i >= 1; i--) {
A_bitset >>= 1;
A_bitset[m] = 1;
A_bitset &= B_bitset[find_pos(A[i])];
ans += A_bitset[1];
}
cout << ans;
完整代码(还没提交检验,过了一些自己设计的数据):
#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>
#include <cstdio>
#define MaxA 150000
#define MaxB 10
using namespace std;
typedef pair<int,int> pii;
int n;
int m;
vector<pii> sorted_B;
vector<int> A(1);
vector<int> B(1);
bitset<MaxB> B_bitset[MaxB];
void init(){
int temp;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> temp;
A.push_back(temp);
}
sorted_B.push_back(make_pair(0,0));
for (int i = 1; i <= m; i++) {
cin >> temp;
B.push_back(temp);
sorted_B.push_back(make_pair(temp,i));
}
sort(sorted_B.begin(),sorted_B.end());
}
void get_B_bitset(){
// 这个函数能够得到标准的bitset,但是没有必要;可以再二分查找上做文章.
// 这里还是写一个得到标准bitset的函数,略微慢一些。如果想要快一点,可以看文末参考博客的代码
for (int i = 1; i <= m; ) {
B_bitset[i] = B_bitset[i-1];
int j = i;
while (j <= m) {
if(sorted_B[i].first == sorted_B[j].first){
B_bitset[i].set(sorted_B[j].second);
j++;
}else break;
}
for (int k = i+1; k < j; k++) {
B_bitset[k] = B_bitset[i];
}
i = j;
}
}
int find_pos(int val){ // 在 B_val_to_pos 中找到最接近val的、<= val的元素(如果出现val比所有B中元素都小,搜索到0)
int head = 0,tail = m;
while (head < tail) {
int mid = (head + tail) / 2;
if(sorted_B[mid].first > val){
tail = mid - 1;
}else if(sorted_B[mid].first < val){
head = mid + 1;
}else{
head = mid;
break;
}
}
while (head > 0) {
if(sorted_B[head].first > val)
head--;
else break;
}
return sorted_B[head].second;
}
int main(){
init();
get_B_bitset();
bitset<MaxB> A_bitset;
int ans = 0;
for (int i = n; i >= n - m + 1; i--) {
A_bitset >>= 1;
A_bitset[m] = 1;
A_bitset &= B_bitset[find_pos(A[i])];
}
for (int i = n - m + 1; i >= 1; i--) {
A_bitset >>= 1;
A_bitset[m] = 1;
A_bitset &= B_bitset[find_pos(A[i])];
ans += A_bitset[1];
}
cout << ans;
return 0;
}
参考