A题想用O(1)求解,结果wa得不行…极大影响了速度。后来改成O(n)就好写多了。年轻人..A题还是老老实实暴力吧
题意
给n个数,求有多少个(l,r) 1<=l < r<=n,令第l到r个数的按位求和的值,大于这(r-l+1)个数中任意一个数
0<=ai<=1e9 1<=n<=200 000
思路
按数位考虑。int型整数有32位。对于某一数位,如果[l…r]这个区间内存在一个数在这个数位上是1,那么按位和在这一数位上就为1。如果所有数在这一数位上都是0,那么按位和在这一数位上就是0
比如11001 01100 按位和是11101
而1001 1101按位和是1101。这不符合题目条件
所以不符合条件的情况是,区间中至少存在一个数,与区间安位和相等(不可能比按位和还大)。不妨叫做X数
考虑i从1到n扫一遍序列,并且记录让ai为这样的数的区间有多少个。最后用所有区间的个数减去这个就行了(就是反面考虑)
从左往右扫的时候数组l[]记录能让这个ai成为区间[l,r]的X数的最左端的l再减去1的值
比如 1011 1000 1001 1101
对于i=4,l[4]就为(2-1)。因为[2,4]的按位和为1101==a[i]。而如果让l=(1-1),那么[1,4]按位和就是1111,a[4]不是X数
pos[j]维护的是i之前,满足数位j为1的最靠右的数是第几个。
1011 1000 1001 1101
i==4时,pos[0] 为3,pos[1]为1
所以如果对于当前X数,它的j数位为0,那么更新l[i] = max(l[i],pos[j])
这是求X数左边能控制的范围的操作,下面还有求右边控制的操作,类似,只是反过来扫
还有一件事,注意X数之前出现和自己相同的值
1011 1000 1101 1001 1101
那么这里i==5的时候,把[3,5]算了一遍。而i==3的时候,又把[3,5]算了一遍,重复计算,出错。所以不如扫l的时候只不算入前面的相同值。
就是确保即使区间有多个X数,但是我们目前扫的a[i]是最左边的X数。这样就不会重复计算
坑点
开 long long
代码
#include <vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include <iostream>
#include <stack>
#include <cmath>
#include <map>
#include <set>
#include <queue>
#include <deque>
using namespace std;
typedef long long ll;
const int maxn = 2*1e5+10;
int a[maxn], l[maxn], r[maxn], pos[40];
map <int,int> M;
int main() {
//freopen("data.in","r",stdin);
ll ans, n;
scanf("%I64d",&n);ans=n*(n+1)/2;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
memset(pos,0,sizeof(pos));
for(int i=1;i<=n;i++){
l[i] = M[a[i]];
for(int j = 0; j < 32 ; j++){
if(!(a[i]& (1<<j)))
l[i] = max(l[i],pos[j]);
}
for(int j = 0; j < 32 ; j++){
if(a[i] & (1<<j))
pos[j] = i;
}
M[a[i]] = i;
}
for(int j=0;j<32;j++) pos[j] = n + 1;
for(int i = n; i > 0; i --){
r[i] = n + 1;
for(int j = 0 ;j < 32; j ++){
if(!(a[i] & (1<<j) ))
r[i] = min(r[i] , pos[j]);
}
for(int j=0;j<32;j++){
if(a[i] & (1<<j))
pos[j] = i;
}
ans -= (ll)(i - l[i]) * (r[i] - i);
}
printf("%I64d",ans);
return 0;
}