题解 P5629 【【AFOI-19】区间与除法】

这题我们可以想到将每个数字变成 (d) 进制的数,消灭数可以看成是 (d) 进制下原数是这个数 (d) 进制下的一段前缀。

于是可以使用 (Trie) 来实现这个过程,于是可以先将这 (m) 个原数 insert,然后再去查询。

一个数有多个前缀,我们可以选择其中最短的一个,因为这样是最优秀的,可以理解为越短的串延伸出来的串就越多,而越长的串则通用性就越差。

于是我们就可以求出是每个数会选择哪个原数进行消灭,我们可以将状态存在一个 long long 里面,需要做到的就是区间或。

无修改,只有区间或,可以想到使用倍增(ST表)来求,因为或有着跟 (min)(max) 一样好的性质。

code(目前的最优解):

#include<bits/stdc++.h>
#define fi first
#define se second
#define LL long long
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size()-1)
#define ms(a,b) memset(a,b,sizeof a)
#define F(i,a,b) for(int i=(a);i<=(b);++i)
#define DF(i,a,b) for(int i=(a);i>=(b);--i)
#define debug(b,a) cerr<<"D:";F(i,1,b-1)cerr<<"   ";cerr<<" "<<#a<<" => "<<(a)<<"  <="<<__LINE__<<"L"<<endl
using namespace std;
inline LL read(){char ch=getchar(); LL w=1,c=0;
	for(;!isdigit(ch);ch=getchar()) if (ch=='-') w=-1;
	for(;isdigit(ch);ch=getchar()) c=(c<<1)+(c<<3)+(ch^48);
	return w*c;
}
const int N=5e5+10,M=65;
int cnt,n,m,d,q,pos[15][M*65],tot,vis[M*25],lg2[N],tmp[N];
LL a[N],b[M];
LL f[25][N];
void insert(LL x,int y){
	int tot=0;
	for(;x;x/=d)tmp[++tot]=x%d;
	int num=0;
	DF(i,tot,1){
		if(vis[num])return;
		if(!pos[tmp[i]][num])pos[tmp[i]][num]=++cnt;
		num=pos[tmp[i]][num];
	}vis[num]=y;
}
int query(LL x){
	int tot=0;
	for(;x;x/=d)tmp[++tot]=x%d;
	int num=0;
	DF(i,tot,1){
		if(vis[num])return vis[num];
		if(!pos[tmp[i]][num])return -1;
		num=pos[tmp[i]][num];
	}return vis[num]?vis[num]:-1;
}
signed main(){
	n=read(),m=read(),d=read(),q=read();
	F(i,2,n)lg2[i]=lg2[i>>1]+1;
	F(i,1,n)a[i]=read();
	F(i,1,m)b[i]=read();
	sort(b+1,b+m+1);
	F(i,1,m)
		insert(b[i],i);
	F(i,1,n){
		int c=query(a[i]);
		if(~c)f[0][i]=(1ll<<c);
	}
	F(i,1,lg2[n])
		F(j,1,n)
			f[i][j]=f[i-1][j]|f[i-1][j+(1<<(i-1))];
	while(q--){
		int l=read(),r=read(),ans=0;
		int dist=(r-l+1);
		LL z=f[lg2[dist]][l]|f[lg2[dist]][r-(1<<lg2[dist])+1];
		for(;z;z&=(z-1))ans++;
		printf("%d
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/zhaohaikun/p/14790447.html