noip模拟赛 区间
分析:要遍历所有的区间,肯定是枚举左端点,然后再枚举右端点.关键是怎么高效地求区间&,|,一般而言是用前缀和的,但是&,|不满足区间可减性,所以可以考虑线段树?这道题不带修改操作,用线段树太浪费了,那么可以用ST表来维护.
查询做到O(1)了,但是怎么快速枚举区间呢?枚举左端点和右端点肯定只能选择一个优化,优化枚举右端点的循环.观察数据范围,100000,很容易想到二分.可以每次固定左端点,然后二分右端点的位置.因为&操作随着区间数的增加而答案减少,|是增加,都满足单调性,所以求满足两个条件的区间的交,统计一下区间的元素个数有多少个就可以了.
#include <cstdio> #include <cmath> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const long long inf = 1LL << 60, mod = 1e9 + 7; typedef long long ll; ll n, a, b, c, d, f1[100010][22], f2[100010][22], s[100010], ans; void init() { for (int j = 1; j <= 21; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) { f1[i][j] = f1[i][j - 1] & f1[i + (1 << (j - 1))][j - 1]; f2[i][j] = f2[i][j - 1] | f2[i + (1 << (j - 1))][j - 1]; } } ll query(ll l, ll r,ll op) { ll k = (ll)((log(r - l + 1)) / log(2.0)); if (op == 1) return f1[l][k] & f1[r - (1 << k) + 1][k]; else return f2[l][k] | f2[r - (1 << k) + 1][k]; } int main() { scanf("%lld%lld%lld%lld%lld", &n, &a, &b, &c, &d); for (int i = 1; i <= n; i++) { scanf("%lld", &s[i]); f1[i][0] = f2[i][0] = s[i]; } init(); for (int i = 1; i <= n; i++) { ll l = i, r = n, temp1 = inf, temp2 = -inf, temp3 = inf, temp4 = -inf; while (l <= r) { ll mid = (l + r) >> 1; if (query(i, mid,1) >= a) { l = mid + 1; temp2 = mid; } else r = mid - 1; } l = i, r = n; while (l <= r) { ll mid = (l + r) >> 1; if (query(i, mid, 1) <= b) { r = mid - 1; temp1 = mid; } else l = mid + 1; } l = i, r = n; while (l <= r) { ll mid = (l + r) >> 1; if (query(i, mid, 2) >= c) { r = mid - 1; temp3 = mid; } else l = mid + 1; } l = i, r = n; while (l <= r) { ll mid = (l + r) >> 1; if (query(i, mid, 2) <= d) { l = mid + 1; temp4 = mid; } else r = mid - 1; } ll ll = max(temp1, temp3), rr = min(temp2, temp4); ans += max((long long)0, rr - ll + 1); ans %= mod; } printf("%lld\n", ans); return 0; }