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;
}
posted @ 2023-10-18 18:42  zsxuan  阅读(2)  评论(0编辑  收藏  举报