【AFOI-19】区间与除法
前言
利益相关:经常骗吃骗喝,也经常被出题人欺负QAQ。
话说出题组的科技树点得有点歪(其实不是一般的歪)...要是认真觉得他们很综合性的话你就输啦!
题解
出题人上课摸鱼出得这题,给我口胡这题问我怎么做,然后我犯蠢了......我瞎扯了一通(自己都心虚的那种)准备水过去...
但是在讨论的过程中,出题人说要用tire树,然后我就突然发现....\(d\)固定的情况下,根本不用去考虑怎么取最优方案来覆盖尽可能多的数,实际上,如果\(b_i\)是\(a\)的原数,\(b_j\)也是\(a\)的原数的话,那么\(min(b_i,b_j)\)就是这三个数的公共原数....
所以......直接枚举\(a_i\),然后依次除\(d\),若除的过程中\(a_i==b_j\),则取最小的\(b_j\),怎么快速找\(a_i==b_j\)呢?用出题人的方案或者数据结构的话太复杂了并且带个\(log\),把\(b_i\)排个序,\(a_i\)在除的过程中递减,\(b_j\)也随之递减,这样来找就行了,这一部分复杂度就是\(O(nlogn)\),取大一点的话其实是\(O(nm)\),但是\(m\)小于等于\(60\)。
剩下的,对于每一个\(a_i\)我们都找到了其唯一对应最小的\(b_j\),\(j\)小于等于\(60\),考虑状压,一个区间里面的状压或起来就是最优方案了,最后随便是\(st\)表还是线段树维护一下区间或值都可以。
\(1.5s\)的话算良心了(嘿嘿)。
#include <bits/stdc++.h>
using namespace std;
void readll(long long &an){
char ch=getchar();an=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')an=(an<<3)+(an<<1)+ch-'0',ch=getchar();
}
void read(int &an){
char ch=getchar();an=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')an=(an<<3)+(an<<1)+ch-'0',ch=getchar();
}
int n,m,d,q;
long long b[65];
long long a[500006];
long long c[500005];
long long t[2000005];
void build(int l,int r,int id){
if(l==r){
t[id]=c[l];
return ;
}
int mid=(l+r)/2;
build(l,mid,id*2);
build(mid+1,r,id*2+1);
t[id]=t[id*2]|t[id*2+1];
}
long long query(int l,int r,int z,int y,int id){
if(l==z&&r==y)return t[id];
int mid=(l+r)/2;
if(mid>=y)return query(l,mid,z,y,id*2);
else if(mid<z)return query(mid+1,r,z,y,id*2+1);
else return query(l,mid,z,mid,id*2)|query(mid+1,r,mid+1,y,id*2+1);
}
int get(long long x){
int ans=0;
while(x){
ans++;
x-=(x&(-x));
}
return ans;
}
int main(){
read(n),read(m),read(d),read(q);
for(int i=1;i<=n;i++){
readll(a[i]);
}
for(int i=1;i<=m;i++){
readll(b[i]);
}
sort(b+1,b+1+m);
int rr=m;
for(int i=1;i<=n;i++)
{
rr=m;
while(a[i]){
while(rr&&b[rr]>a[i])rr--;
if(b[rr]==a[i])c[i]=(1ll<<(rr));
a[i]/=d;
}
}
build(1,n,1);
for(int i=1;i<=q;i++){
int l,r;
read(l),read(r);
printf("%d\n",get(query(1,n,l,r,1)));
}
return 0;
}