HDU 4737 A Bit Fun 解题报告

题目

题意:

有n个数,求有多少个数对(i, j) 使得 ai|ai+1|ai+2| ... | aj的值小于m。

题解:

这题有好多种解法:


解法1:

边读边做,将数拆开成二进制,用pre数组表示在这一位上最近出现1的位置。当做到第j个数的时候,固定数对的右端点为j,那么就在左边找左端点i。

从高位到低位和m比较,如果这一位上m是1,那么如果i~j间都是0,则肯定比m小,任取哪个做左端点都可以;如果i~j间有1,则这一位和m相等,看下一位。这一位上m是同理。


解法2:

枚举右端点,二分最远能满足要求的左端点。这里可以用RMQ的倍增法,预处理一个个区间的或值,那么查询就是log n的。


解法3:

还是先拆开数,记当前区间每一位上1的个数。如果右端点为j时,最远左端点为i,或值为x,那么右端点为j+1时,或值为x=x|aj+1。如果x不小于m,则右移i,同时减掉对应位置的1.当这一位1的个数为0时,x就剪掉对应值,直到x小于m为止。


解法1和解法3复杂度都是n*log n的,解法2是n*log n*log n,但是比解法1和解法2小个常数32,所以都是差不多的。

比赛时我用的解法1,后来才想到其它的。


//Time:609ms
//Memory:16416KB
//Length:1131B
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define MAXN 100010
char str[MAXN];
int arr[MAXN][40],ma[40];
int pre[MAXN],num[MAXN];
void cha(int n,int num[],int h)
{
    for(int i=0;i<32;++i)   num[i]=n&1,n>>=1;
    for(int i=0;i<32;++i)
        if(num[i])  pre[i]=h;
}
int cal(int l,int r)
{
    int ret=0;
    for(int i=31;i>=0&&l<=r;--i)
        if(ma[i]==1)
        {
            ret+=max(0,r-max(pre[i],l-1));
            r=min(r,pre[i]);
        }
        else    l=max(l,pre[i]+1);
    return ret+max(r-l+1,0);
}
int main()
{
    //freopen("/home/moor/Code/output","r",stdin);
    int ncase,n,m;
    long long ans=0;
    scanf("%d",&ncase);
    for(int hh=1;hh<=ncase;++hh)
    {
        printf("Case #%d: ",hh);
        scanf("%d%d",&n,&m);
        cha(m-1,ma,0);
        memset(pre,-1,sizeof(pre));
        ans=0;
        for(int i=0;i<n;++i)
        {
            int tmp;
            scanf("%d",&tmp);
            cha(tmp,arr[i],i);
            ans+=cal(0,i);
        }
        cout<<ans<<'\n';
    }
    return 0;
}


你可能感兴趣的:(HDU 4737 A Bit Fun 解题报告)