Codeforces #441 Div2 F. High Cry

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值