传送门
题目大意
有一个长度为 的数组 ,有 次操作,操作分两类:
- 将 修改成 ;
- 给定 和 ,询问有多少个区间 满足 且 按位或和至少为 。(即: )
其中 是一开始给定的一个整数。
; 。
思路
考虑 Special Instance。
当没有修改操作时
我们称一个区间是好的当且仅当这个区间中的所有数按位或和至少为 。一个显然的结论是,若 是好的,那么 也是好的;若 不是好的,那么 也不是好的。我们可以求出每个左端点有哪些右端点使得所选区间是好的,这个可以用双指针 预处理。
将查询离线,把查询的左端点拆成前缀和相减的形式,然后用 CDQ 分治。时间复杂度 。
然而并没有什么卵用……
基于分治的做法
考虑分治。将 分成 和 后只考虑计算跨过区间的贡献。结合上面的结论,我们可以只花费 就计算出跨区间的答案。一次查询的时间复杂度为 。
设 表示 的按位或和(即前缀和)。发现,从 到 只会变化 次,因此不会有一个二进制位被删除。这个性质对子区间、后缀和同样成立。考虑用这个性质优化分治查询的过程。对于一次扫描,我们只需要花费 的时间。
考虑使用线段树,因为线段树本身是一个分治结构。我们保存这样一个东西:一个区间的前缀和中第一次出现位 的位置,一个区间的后缀和中第一次(靠右)出现位 的位置。用这两个东西就能够在 的时间复杂度内算出跨过中点的答案数量,我们把它保存起来,并且区间求和。另外,我们能够在 的时间复杂度内合并子区间的这两个东西。查询时需要合并 个区间,做法类似。因此算法的时间复杂度为 。
参考代码
注意 的情况。
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
putchar('\n');
}
const int maxn = int(1e5) + 5;
int n, m, x;
int a[maxn];
class SegTree
{
static inline int code(int l, int r)
{
return (l + r) | (l != r);
}
struct Node
{
int pre[20];
int suf[20];
LL ans;
} nodes[maxn * 2];
static LL calc(const Node& lc, const Node& rc, int l, int r, int mid)
{
int asuf[21]; asuf[0] = 0;
int apre[21]; apre[0] = 0;
int sufsum[21];
int presum[21];
for (int i = 0; i < 20; i++)
if (lc.suf[i])
asuf[++asuf[0]] = lc.suf[i];
for (int i = 0; i < 20; i++)
if (rc.pre[i] <= n)
apre[++apre[0]] = rc.pre[i];
if (!asuf[0] && !apre[0]) return 0;
std::sort(asuf + 1, asuf + 1 + asuf[0]);
std::sort(apre + 1, apre + 1 + apre[0]);
asuf[0] = std::unique(asuf + 1, asuf + 1 + asuf[0]) - (asuf + 1);
apre[0] = std::unique(apre + 1, apre + 1 + apre[0]) - (apre + 1);
LL ret = 0;
if (!asuf[0])
{
presum[1] = a[apre[1]];
for (int i = 2; i <= apre[0]; i++)
presum[i] = presum[i - 1] | a[apre[i]];
int cnt = 0;
while (cnt <= apre[0] && (cnt ? presum[cnt] : 0) < x)
cnt++;
if (cnt <= apre[0])
ret += (LL)(mid - l + 1) * (r - (cnt ? apre[cnt] - 1 : mid));
}
else if (!apre[0])
{
sufsum[asuf[0]] = a[asuf[asuf[0]]];
for (int i = asuf[0] - 1; i; i--)
sufsum[i] = sufsum[i + 1] | a[asuf[i]];
int cnt = asuf[0] + 1;
while (cnt && (cnt <= asuf[0] ? sufsum[cnt] : 0) < x)
cnt--;
if (cnt)
ret += (LL)(asuf[cnt] - l + 1) * (r - mid);
}
else
{
sufsum[asuf[0]] = a[asuf[asuf[0]]];
for (int i = asuf[0] - 1; i; i--)
sufsum[i] = sufsum[i + 1] | a[asuf[i]];
presum[1] = a[apre[1]];
for (int i = 2; i <= apre[0]; i++)
presum[i] = presum[i - 1] | a[apre[i]];
int cnt = 0;
for (int i = 1; i <= asuf[0] && cnt <= apre[0]; i++)
{
while (cnt <= apre[0] && (cnt ? (sufsum[i] | presum[cnt]) : sufsum[i]) < x)
cnt++;
if (cnt > apre[0])
break;
ret += (LL)(asuf[i] - (i == 1 ? l - 1 : asuf[i - 1])) * (r - (cnt ? apre[cnt] - 1 : mid));
}
if (asuf[asuf[0]] != mid)
{
while (cnt <= apre[0] && (cnt ? presum[cnt] : 0) < x)
cnt++;
if (cnt <= apre[0])
ret += (LL)(mid - asuf[asuf[0]]) * (r - (cnt ? apre[cnt] - 1 : mid));
}
}
return ret;
}
void cover(int l, int r)
{
Node& t = nodes[code(l, r)];
t.ans = a[l] >= x;
for (int i = 0; i < 20; i++)
if (a[l] & (1 << i))
t.pre[i] = l;
else
t.pre[i] = n + 1;
for (int i = 0; i < 20; i++)
if (a[l] & (1 << i))
t.suf[i] = l;
else
t.suf[i] = 0;
}
Node update(const Node& lc, const Node& rc, int l, int r, int mid)
{
Node t;
for (int i = 0; i < 20; i++)
t.pre[i] = std::min(lc.pre[i], rc.pre[i]);
for (int i = 0; i < 20; i++)
t.suf[i] = std::max(lc.suf[i], rc.suf[i]);
t.ans = lc.ans + rc.ans + calc(lc, rc, l, r, mid);
return t;
}
int g_Pos, g_Val, g_L, g_R;
LL ans;
void modify_(int l, int r)
{
if (l == r)
{
a[l] = g_Val;
cover(l, r);
return;
}
int mid = (l + r) >> 1;
if (g_Pos <= mid) modify_(l, mid);
else modify_(mid + 1, r);
nodes[code(l, r)] = update(nodes[code(l, mid)], nodes[code(mid + 1, r)], l, r, mid);
}
Node query_(int l, int r)
{
if (g_L <= l && r <= g_R)
{
const Node& t = nodes[code(l, r)];
ans += t.ans;
return t;
}
int mid = (l + r) >> 1;
if (g_R <= mid)
return query_(l, mid);
else if (g_L > mid)
return query_(mid + 1, r);
else
{
Node L = query_(l, mid);
Node R = query_(mid + 1, r);
ans += calc(L, R, std::max(g_L, l), std::min(g_R, r), mid);
return update(L, R, std::max(g_L, l), std::min(g_R, r), mid);
}
}
public:
void build(int l, int r)
{
if (l == r)
{
cover(l, r);
return;
}
int mid = (l + r) >> 1;
build(l, mid);
build(mid + 1, r);
nodes[code(l, r)] = update(nodes[code(l, mid)], nodes[code(mid + 1, r)], l, r, mid);
}
void modify(int pos, int val)
{
g_Pos = pos;
g_Val = val;
modify_(1, n);
}
LL query(int l, int r)
{
g_L = l;
g_R = r;
ans = 0;
query_(1, n);
return ans;
}
} st;
void run()
{
n = readIn();
m = readIn();
x = readIn();
for (int i = 1; i <= n; i++)
a[i] = readIn();
if (!x)
{
while (m--)
{
int type = readIn();
if (type == 1)
{
int pos = readIn();
int val = readIn();
}
else if (type == 2)
{
int l = readIn();
int r = readIn();
LL len = r - l + 1;
printOut((len + 1) * len >> 1);
}
}
}
else
{
st.build(1, n);
while (m--)
{
int type = readIn();
if (type == 1)
{
int pos = readIn();
int val = readIn();
st.modify(pos, val);
}
else if (type == 2)
{
int l = readIn();
int r = readIn();
printOut(st.query(l, r));
}
}
}
}
int main()
{
run();
return 0;
}
总结
这道题主要是要发现按位或前缀和只有 种取值,再加上分治做法,应该就能想到用线段树来做。