G.Greater and Greater - 2020牛客暑期多校训练营(第二场) (位运算,二分查找)

G.Greater and Greater - 2020牛客暑期多校训练营(第二场) (位运算,二分查找)

截屏2020-07-15 上午9.31.36

题目下载:https://files.cnblogs.com/files/popodynasty/G.Greater_and_Greater.pdf.zip

题目大意

给定两个数列A、B,其中数列A的长度一定不小于数列B的长度。求数列A中有多少这样的连续子数列,其数列长度和B数列相同,且子数列中的各个元素均不小于B数列中对应位置的元素。

题解

截屏2020-07-15 上午9.52.28

思路

这题无法避免将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

截屏2020-07-15 上午9.58.06

将这些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 58 5 7

其实样例数据等价于:

A: 1 3 2 3 3 3

B: 2 3 3

符合题意的子数列为 2 3 33 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,由于43的相邻元素且更大,所有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;
}

参考

https://www.cnblogs.com/HexQwQ/p/13296366.html

posted @ 2020-07-14 10:07  popozyl  阅读(286)  评论(0编辑  收藏  举报