题目:
给你一个长度为N的数组a,求有多少对i<=j满足 g c d ( a i , a i + 1 , a i + 2 , . . . , a j ) x o r ( a i o r a i + 1 o r a i + 2 o r . . . a j ) = k gcd(a_i,a_{i+1},a_{i+2},...,a_j)xor(a_i or a_{i+1} or a_{i+2} or ... a_j) =k gcd(ai,ai+1,ai+2,...,aj)xor(aiorai+1orai+2or...aj)=k
注: x o r , o r xor,or xor,or都指位运算
数据范围: n ≤ 500000 , 1 ≤ a [ i ] ≤ 500000 n\le 500000,1\le a[i]\le 500000 n≤500000,1≤a[i]≤500000
题解:
这个题思维难度其实不高主要是要:
-
有一个意识(也可以说是套路):求多个数的gcd时,添加一个数,gcd要么不变,要么降低,且最多降低 l o g n log n logn次就变成1
-
会二分求出像这样的单调函数的每个断层:
-
会灵活运用st表
显然说到这这题也就没什么可做的了,用st表维护区间Gcd和Or值。对于每个区间左端点,Gcd的值都为单调递减断层函数(且最多有log n个断层),Or的值都为单调递增断层函数。直接枚举区间左端点,二分求出Gcd的每个断层,因为 G c d x o r O r = k Gcd xor Or=k GcdxorOr=k,所以 O r = k G c d Or=k^Gcd Or=kGcd,就可以二分找到每个Gcd断层对应的Or断层,然后根据两个断层统计答案即可。
时间复杂度: O ( n l o g 2 n ) O(nlog^2 n) O(nlog2n)
代码:
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
#define N 500005
int n,k;
int ai[N],Gcd[N][25],Or[N][25],Log[N];
long long ans;
inline int getGcd(int x,int y) //O(1)求[x,y]的Gcd
{
int a=Log[y-x+1];
return __gcd(Gcd[x][a],Gcd[y-(1<<a)+1][a]);
}
inline int getOr(int x,int y) //O(1)求[x,y]的Or
{
int a=Log[y-x+1];
return (Or[x][a]|Or[y-(1<<a)+1][a]);
}
int main()
{
scanf("%d%d",&n,&k);
int np=0;
for(int i=1;i<=n;i++){
if(i==(1<<np))
Log[i]=np++;
else
Log[i]=np-1;
}
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
Or[i][0]=Gcd[i][0]=x;
}
for(int a=1;a<=20;a++)
for(int i=1;i+(1<<a)-1<=n;i++)
{
Gcd[i][a]=__gcd(Gcd[i][a-1],Gcd[i+(1<<(a-1))][a-1]);
Or[i][a]=Or[i][a-1]|Or[i+(1<<(a-1))][a-1];
}
for(int now=1;now<=n;now++){ //固定左指针为now
int nowGcd=Gcd[now][0],rpGcd=now; //now为目前右指针范围到左指针的值,rp为目前右指针范围的左边界
while(true)
{
int l=rpGcd,r=n;
while(l<r) //二分Gcd右指针范围的右边界 (Gcd右指针递减)
{
int mid=((l+r)>>1);
if(r==l+1) mid++;
int tnGcd=getGcd(now,mid);
if(tnGcd<nowGcd)
r=mid-1;
else
l=mid;
}
int nowOr=(k^nowGcd),yoGcd=l,rpOr,yoOr;
/**/
l=now,r=n;
while(l<r) //二分Or右指针范围的左边界 (Or右指针递增)
{
int mid=((l+r)>>1);
int tnOr=getOr(now,mid);
if(tnOr>nowOr)
r=mid-1;
else if(tnOr==nowOr)
r=mid;
else
l=mid+1;
}
rpOr=l,r=n;
while(l<r) //二分Or右指针范围的右边界 (Or右指针递增)
{
int mid=((l+r)>>1);
if(r==l+1) mid++;
int tnOr=getOr(now,mid);
if(tnOr>nowOr)
r=mid-1;
else
l=mid;
}
yoOr=l;
int zuo=max(rpGcd,rpOr),you=min(yoGcd,yoOr);
if(you>=zuo && getOr(now,rpOr)==nowOr) ans+=you-zuo+1;
rpGcd=yoGcd+1;
if(rpGcd>n) break;
nowGcd=getGcd(now,rpGcd);
}
}
cout<<ans<<endl;
return 0;
}