题目
题意:
有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; }