Codeforces 1168C And Reachability
题目链接
https://codeforces.com/contest/1168/problem/C
题目大意
给你一个长度为 $n$ 的数列 $a$ ,$q$ 次询问,问是否存在数列 $p$ 使得有 $x=p_1<p_2<\cdots<p_k=y$,且 $a_{p_i} \& a_{p_{i+1}}>0(1 \leq i < k)$ 成立。
题目分析
看到有 $q$ 次询问,可以想到需要预处理一些东西之后迅速查询。观察给的式子,可以发现,如果 $a_x$ 能走到 $a_y=2^i$ ,那么所有包含 $2^i$ 且位置大于等于 $y$ 的都可以走到。因此显然可以将所有的位拆解,对于每一个位置求走到 $2^i$ 的最小需要的右端点位置。求这个显然只需要记录一下每一位出现的最后一次的位置,再 $DP$ 一下就可以。
查询的时候枚举一下是通过哪个 $2^i$ 走过去的,判断一下右端点位置是否满足要求即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
const int inf = 0x3f3f3f3f;
int n, m;
int a[maxn];
int f[maxn][21];
int last[21];
int main(){
int i, j, k;
int x, y;
memset(f, 0x3f, sizeof(f));
memset(last, 0x3f, sizeof(last));
scanf("%d%d", &n, &m);
for(i=1;i<=n;i++){
scanf("%d", &a[i]);
}
for(i=n;i>0;i--){
for(j=0;j<=20;j++){
if((a[i] >> j) & 1){
f[i][j] = i;
if(last[j] != inf){
for(k=0;k<=20;k++){
f[i][k] = min(f[i][k], f[last[j]][k]);
}
}
last[j] = i;
}
}
}
while(m--){
scanf("%d%d", &x, &y);
bool flag = false;
for(j=0;j<=20;j++){
if((a[y] >> j) & 1){
if(y >= f[x][j]){
flag = true;
break;
}
}
}
if(flag){
printf("Shi\n");
}else{
printf("Fou\n");
}
}
return 0;
}