「CF875D High Cry」
题目大意
给出一个序列 \(a\),求有多少子串满足子串的 \(\max\) 严格小于子串中所有数的或值.
分析
显然一个子串的或者肯定是大于等于子串的 \(\max\),那么问题就变成了要求出有多少的子串的或值等于子串的 \(\max\).再经过简单的分析,不难发现如果子串最大值的二进制中第 \(k\) 位为 \(0\),但子串中存在某数二进制下第 \(k\) 位的值为 \(1\),那么这个子串的或值必然大于最大值(最大值与这个数的或值必然大于最大值).
考虑对每个数计算以当前数为最大值时的不合法的子串数,即计算有多少子串的最大值在某个位置(\(i\))时有异或值等于最大值.那么这个子串的左端点(\(l\)),右端点(\(r\))需要满足以下条件:
- \(l\leq i\leq r\)(\(i\) 位置必须在子串中)
- \(\max\limits_{l\leq j\leq r}a_j=a_l\lor a_{l+1}\lor a_{l+2}\lor\dots\lor a_{r-2}\lor a_{r-1}\lor a_r=a_i\)(子串的最大值等于子串的异或值等于 \(a_i\))
于是可以处理出以某个数为最大值的区间以及对于某个数所不存在的二进制的位置上一次和下一次出现的位置,这样就可以通过简单计算计算出结果.
但是如果在序列中存在多个相同的数可能会出点小问题.
如第二个样例中的 3 3 3 3
,直接计算某个数为最大值的区间会计算的结果都是 \([1,4]\),但这样在后面的计算中会出问题,但 \([1,4],[2,4],[3,4],[4,4]\) 就不会有问题了,所以可以强制某个数是某个区间的最大值需要满足在这个数之前(或者之后)不存在大于等于这个数的数.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=2e5+5;
int n;
int place[100];
struct Number
{
int num,left,right,l,r;//每个位置的数,这个数不存在的二进制位置上一次出现的位置,下一次出现的位置,这个为最大值的区间的左端点,右端点
}arr[MAXN];
int st[MAXN],top=0;
int main()
{
Read(n);
REP(i,1,n)
{
Read(arr[i].num);
arr[i].left=0;
arr[i].right=n+1;
REP(j,0,31)
{
if(arr[i].num&(1<<j))//如果这个数的二进制中存在第 j 位,那么就把第 j 位的上一次出现位置变为 i
{
place[j]=i;
}
else
{
arr[i].left=Max(arr[i].left,place[j]);//否则就与最近出现位置取更近的位置
}
}
while(top&&arr[st[top]].num<arr[i].num)//计算左端点时用 <,右端点用 <=(反之同理)
{
top--;
}
arr[i].l=st[top];//单调栈处理出这个数为最大值的区间的左端点(右端点同理)
st[++top]=i;
}
top=0;
REP(i,0,31)
{
place[i]=n+1;
}
DOW(i,n,1)//同理
{
REP(j,0,31)
{
if(arr[i].num&(1<<j))
{
place[j]=i;
}
else
{
arr[i].right=Min(arr[i].right,place[j]);
}
}
while(top&&arr[st[top]].num<=arr[i].num)
{
top--;
}
arr[i].r=st[top]?st[top]:n+1;
st[++top]=i;
}
long long answer=1ll*n*(n+1)>>1;//总方案数
REP(i,1,n)
{
answer-=1ll*(i-Max(arr[i].l,arr[i].left))*(Min(arr[i].r,arr[i].right)-i);//需要同时满足两个限制,所以直接对区间的端点取距离 i 近的那个就行了
}
Writeln(answer);
return 0;
}