传送门
解析:
这道题虽然说容斥和分治的复杂度都是 O ( n ) O(n) O(n)套 l o g log log,但是容斥要处理二进制位,套的 l o g log log是值域的 l o g log log,而分治只有 O ( l o g n ) O(logn) O(logn),所以分治实际上是要比容斥快很多的。(也就快了大概一倍吧)
思路:
考虑每次处理 < l , r > <l,r> <l,r>,选择出一个分治中心 m i d mid mid,然后处理左端点在 < l , m i d > <l,mid> <l,mid>中,右端点在 < m i d + 1 , r > <mid+1,r> <mid+1,r>中的所有情况,然后递归处理 < l , m i d > <l,mid> <l,mid>和 < m i d + 1 , r > <mid+1,r> <mid+1,r>两个区间,显然这样是不重不漏的。
考虑这道题直接统计合法的方案数不太现实,所以转换一下,统计不合法的方案数,反正总方案数是 n 2 / 2 n^2/2 n2/2,但是显然左右端点重合的情况不可能合法,所以直接总方案数是 n × ( n − 1 ) / 2 n\times(n-1)/2 n×(n−1)/2,然后分治下去。
首先位或和
m
a
x
max
max这两个操作都满足区间加法,那么处理所有点到分治中心的运算结果前缀和,那么需要查询某个区间(横跨分治中心)的时候就可以直接
O
(
1
)
O(1)
O(1)了
考虑处理每一个
l
≤
i
≤
m
i
d
l\leq i \leq mid
l≤i≤mid的位置
i
i
i,在
<
m
i
d
+
1
,
r
>
<mid+1,r>
<mid+1,r>中有多少个合法的右端点。
接下来是我们每次分治做到
O
(
n
)
O(n)
O(n)的关键,解的单调性的证明。
首先所有前缀(后缀)位或和以及区间
m
a
x
max
max都是单调不减的。
显然对于任意区间 < i , j > <i,j> <i,j>,都有 g i , j ≥ f i , j g_{i,j}\geq f_{i,j} gi,j≥fi,j,所以不合法的情况只有 g i , j = f i , j g_{i,j}=f_{i,j} gi,j=fi,j。
首先,如果一个区间满足上述性质,则对于它某个端点到分治中心(不妨假设是左端点)必然有 f i , m i d = g i , m i d f_{i,mid}=g_{i,mid} fi,mid=gi,mid,即它的左部分本身就不是一个合法区间。证明十分显然,因为现在考虑的是以当前左端点为最大值。
然后我们一直右移右端点 j j j直到下一个位置越界或者变得合法。
那么 < m i d + 1 , j > <mid+1,j> <mid+1,j>中的所有端点都可以作为当前不合法区间的右端点。
证明很简单,由于我们右移的策略就是新增的 j j j的两个运算不会影响原来的运算结果,并且远离分治中心的结果是单调的,所以解也是单调的。
注意处理右半边的时候不能考虑最大值相等的情况,因为相等的情况在左半部分已经考虑过了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
inline int getint(){
re int num;
re char c;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return num;
}
cs int N=1000006;
int f[N],g[N],s[N];
ll ans;
inline void solve(int l,int r){
if(l==r)return ;
int mid=(l+r)>>1;
solve(l,mid);solve(mid+1,r);
f[mid]=g[mid]=s[mid];
for(int re i=mid-1;i>=l;--i){
f[i]=max(f[i+1],s[i]);
g[i]=g[i+1]|s[i];
}
f[mid+1]=g[mid+1]=s[mid+1];
for(int re i=mid+2;i<=r;++i){
f[i]=max(f[i-1],s[i]);
g[i]=g[i-1]|s[i];
}
for(int re i=mid,j=mid;i>=l;--i)
if(f[i]==g[i]){
while(j<r&&f[j+1]<=f[i]&&(g[i]|g[j+1])==g[i])++j;
ans-=j-mid;
}
for(int re i=mid+1,j=mid+1;i<=r;++i)
if(f[i]==g[i]){
while(j>l&&f[j-1]<f[i]&&(g[i]|g[j-1])==g[i])--j;
ans-=mid-j+1;
}
}
int n;
signed main(){
n=getint();
for(int re i=1;i<=n;++i)s[i]=getint();
ans=(ll)n*(n-1)/2;
solve(1,n);
cout<<ans;
return 0;
}