题目 【传送门】
有 t 组数据。每组数据给定长度为 n 的数组 a 和 q 次询问。
我们定义 f(l,r)(1≤l≤r≤n) 表示 al&al+1&…&ar−1&ar 的结果。其中,& 表示位与运算。
对于每次询问,将给定 l,k。请你找到最大的 r 使得f(l,r)≥k。如果无解,输出 1
分析
因为是 &,所以当前位上只要有一个0就为0,不难发现,l相同时,r越小越易满足大于k的条件 那么可以二分 r,然后进行check
接下来,我们来思考怎么check:
看到位运算,很多人应该会想到拆位,于是我们可以用num[i][j]来表示第i个数的第j位是0还是1, 然后对每一位进行前缀求和
前缀和的用途就是可以快速求出 [ l , r ] 中1是否连续 若连续,则对k的贡献为
Code
闭坑:记得加快读,初始化尽量别用memset,否则 ——超时
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,T,x,m,k,L,l,r,sum[200005][32];
ll read()
{
ll s=0; char c=getchar(); bool f=0;
for(;!isdigit(c);c=getchar()) f^=!(c^45);
for(;isdigit(c);c=getchar()) s=(s<<1)+(s<<3)+(c^48);
if(f) s=-s; return s;
}
//快读
bool check(ll t)
{
ll s=0;
for(int i=0;i<=30;i++)
if(sum[t][i]-sum[L-1][i]==t-L+1) s+=(1ll<<i);//判断是否为连续的1
return (s>=k);
}
int main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
for(int j=0;j<=30;j++)
sum[i][j]=0;
for(int i=1;i<=n;i++)
{
x=read();
for(int j=0;j<=30;j++)
{
if((x>>j)&1) sum[i][j]=1;
sum[i][j]+=sum[i-1][j];
}
}
scanf("%lld",&m);
for(int i=1;i<=m;i++)
{
L=read(),k=read(); l=L,r=n,x=-1;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)) x=mid,l=mid+1;
else r=mid-1;
}
cout<<x<<" ";
} cout<<"\n";
}
return 0;
}