Codeforces Round 882 (Div. 2) B. Hamon Odyssey
给一个长为 \(n\) 的数组 \(a_1, a_2, \cdots, a_n\) 。定义 \(f(l, r) = \&_{i=l}^{r} a_i\) 。
你需要对 \(a\) 进行分段,使得各段的 \(f(l, r)\) 之和最小。
在各段 \(f(l, r)\) 之和最小的情况下,尽可能分出更多的段。
输出满足上述条件下可分的段数。
一:\(f(1, n) > 0\) 。
显然 \(f(1, x) \geq f(1, n), f(x + 1, n) \geq f(1, n)\) 。于是分 \(1\) 段使得各段 \(f(l, r)\) 之和最小。
二:\(f(1, n) = 0\)。
则可以双指针从左往右扫,统计出所有段的”按位与和“为 \(0\) 的段 \([l_i, r_i]\) ,总数为 \(cnt\) 。
此处双指针只需要从一边扫描的正确性可以证明:
- 若从左往右扫统计出的贡献为 \(0\) 的段数为 \(cnt\) ,显然从右往左扫的段 \(\geq cnt\) 。
- 若从右往左扫统计出的贡献为 \(0\) 的段数为 \(cnt\) ,显然从左往右扫的段 \(\geq cnt\) 。
- 于是从左往右扫的段数等于从右往左扫的段数。\(qed\)。
最后一段之后的元素可以“按位与”入这段,贡献为 \(0\) ,于是答案为 \(cnt\) 。
view
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
void solve(){
int n; std::cin >> n;
std::vector<int> a(n+1);
for (int i = 1; i <= n; i++) std::cin >> a[i];
int AndSum = a[1];
for (int i = 2; i <= n; i++) AndSum &= a[i];
if (AndSum > 0) std::cout << 1 << '\n';
else {
int cnt = 0;
for (int i = 1; i <= n; i++) {
int j = i + 1;
int AndSum = a[i];
while (j <= n && AndSum != 0) {
AndSum &= a[j], j += 1;
}
j -= 1;
cnt += AndSum == 0;
i = j;
}
std::cout << cnt << '\n';
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
int _ = 1; std::cin >> _;
while (_--) solve();
return 0;
}
——现在有无穷的乐子,但不该透支向未来。
——永远是挑战而不是练习,下次一定更好。