传送门
解析:
这道题虽然说容斥和分治的复杂度都是 套 ,但是容斥要处理二进制位,套的 是值域的 ,而分治只有 ,所以分治实际上是要比容斥快很多的。(也就快了大概一倍吧)
思路:
考虑每次处理 ,选择出一个分治中心 ,然后处理左端点在 中,右端点在 中的所有情况,然后递归处理 和 两个区间,显然这样是不重不漏的。
考虑这道题直接统计合法的方案数不太现实,所以转换一下,统计不合法的方案数,反正总方案数是 ,但是显然左右端点重合的情况不可能合法,所以直接总方案数是 ,然后分治下去。
首先位或和
这两个操作都满足区间加法,那么处理所有点到分治中心的运算结果前缀和,那么需要查询某个区间(横跨分治中心)的时候就可以直接
了
考虑处理每一个
的位置
,在
中有多少个合法的右端点。
接下来是我们每次分治做到
的关键,解的单调性的证明。
首先所有前缀(后缀)位或和以及区间
都是单调不减的。
显然对于任意区间 ,都有 ,所以不合法的情况只有 。
首先,如果一个区间满足上述性质,则对于它某个端点到分治中心(不妨假设是左端点)必然有 ,即它的左部分本身就不是一个合法区间。证明十分显然,因为现在考虑的是以当前左端点为最大值。
然后我们一直右移右端点 直到下一个位置越界或者变得合法。
那么 中的所有端点都可以作为当前不合法区间的右端点。
证明很简单,由于我们右移的策略就是新增的 的两个运算不会影响原来的运算结果,并且远离分治中心的结果是单调的,所以解也是单调的。
注意处理右半边的时候不能考虑最大值相等的情况,因为相等的情况在左半部分已经考虑过了。
代码:
#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;
}