新生赛题解(最大的最短或子段)

\(题解:\)
\(我们考虑到其最大值是固定的\)
\(所以我们优先求出其最大值\)
\(考虑到其答案肯定是一个固定的区间\)
\(对于每个区间 如果将其范围缩小 它的取值或值只会单调不增\)
\(那么对于每个区间 首先选定其左端点 然后我们向右不断拓展 区间长度\)
\(一直到其区间或值为最大值\)
\(但是我们怎么维护区间值的增删呢\)
\(考虑给最大值的二进制的每一位进行标记(这是算法竞赛中很重要的一种思想拆位)\)
\(建议各位新生以后在遇到 区间& | ^问题时首先思考能否进行拆位 进行加速\)
\(因为最大值 很明显是固定的\)
\(所以我们维护其区间二进制每一位出现的次数\)
\(如果最大值上存在的一位的二进制位消失不见 那么这个区间不是最大区间 考虑继续拓展区间长度\)
\(综合复杂度为O(nlogn)\)
\(在出数据时考虑到新生很难想到拆位思想 所以放了一些优化暴力都可通过的数据(并未太过毒瘤)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;

//---------------------------//
#ifdef LOCAL
template <class... Args> void debug(const Args&... args) { ((cerr << args << ", "), ...) << '\n'; }
#define debug(args...) cerr << #args << ": ", debug(args)
#define debugsq(x) cerr << #x << ": ["; for (auto i : x) cerr << i << ' '; cerr << "]\n";
#define debugmp(x) cerr << #x << ": [ "; for (auto [i, j] : x) cerr << '[' << i << "," << j << "] "; cerr << "]\n";
#else
#define debug(...) ;
#define debugsq(...) ;
#define debugmp(...) ;
#endif
//---------------------------//

// 核心解法函数
void solve() {
    int n; 
    cin >> n;
    vector<int> a(n + 2, 0), cnt(32, 0); // a 用于存储输入数组,cnt 用于记录每个二进制位的出现次数
    int mx = 0, cnt_gg = 0; // mx 存储整个数组的最大或值,cnt_gg 记录需要满足的二进制位数
    
    // 计算数组的整体最大或值
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        mx |= a[i];
    }

    // 找出 mx 的所有二进制位中为 1 的位数,并初始化 cnt 数组和 cnt_gg 计数器
    for (int i = 0; i < 32; i++) {
        if (mx >> i & 1) {
            cnt[i] = 1;
            cnt_gg++;
        }
    }

    // debugsq(cnt); // 调试:输出 cnt 数组的状态
    fill(cnt.begin(), cnt.end(), 0); // 重置 cnt 数组为 0
    int l = n, r = n, ans = n; // 双指针窗口的左右端点初始化为数组末尾,ans 初始化为数组长度

    // 反向滑动窗口,寻找最短子段
    while (l > 0 && r > 0) {
        // 减去 r+1 元素的二进制位贡献
        for (int i = 0; i < 32; ++i) {
            if ((a[r + 1] >> i) & 1) { // 若 a[r+1] 的第 i 位为 1
                if (cnt[i] == 1) cnt_gg++; // 若该位在 cnt 数组中的计数为 1,cnt_gg 增加
                cnt[i]--; // 将该位计数减 1
            }
        }

        // 调整左边界 l
        if (l > r) l = r;
        
        // 将 l 左移,直到窗口中的或值覆盖 mx 的所有 1 位
        while (cnt_gg > 0 && l > 0) {
            for (int i = 0; i < 32; ++i) {
                if ((a[l] >> i) & 1) { // 若 a[l] 的第 i 位为 1
                    if (cnt[i] == 0) cnt_gg--; // 若该位原本在 cnt 中为 0,则 cnt_gg 减少
                    cnt[i]++; // 增加该位计数
                }
            }
            --l; // 移动左端点
        }

        // 若此时 cnt_gg 为 0,表示当前窗口包含了 mx 所有的 1 位
        if (cnt_gg == 0) {
            ans = min(ans, r - l); // 更新最短长度
        }

        --r; // 缩小右边界
    }
    cout << ans << endl; // 输出结果
}

signed main() {
    ios::sync_with_stdio(0); 
    cin.tie(0); 
    cout.tie(0);
    
    int _ = 1;
    // cin >> _; // 若有多组测试数据,取消注释此行
    while (_--) {
        solve();
    }
}

posted @ 2024-11-03 22:07  archer2333  阅读(28)  评论(0)    收藏  举报