Brief Intro:
1操作:添加一个数
2操作:寻找一个数v,ki∣GCD(xi,v), xi+v≤si, 且v^xi最大
Algorithm:
以前做的关于异或和字符串的题目比较少,这题就当是补基础了吧
此题对v的限制条件很多,其中V、Xi异或值最大我们发现是一类经典问题
建立Trie树,贪心寻找答案即可
为了使答案满足ki∣GCD(xi,v),我们对1e5个数都各自建立一棵Trie树,其中存放这个数的所有倍数
为了满足另一个条件V<=Si-Xi,我们对于每棵树上的每个节点维护一个min值,存放能到达此点的最小的数列中的数
这样也顺便解决了是K的倍数但不属于数列的数的问题(min数组初始化INF)
这样在查找时在满足min值<=Si-Xi的前提下贪心即可
Code:
#include <bits/stdc++.h> using namespace std; const int MAXN=1e5+10; const int INF=1<<27; bool mark[MAXN]; int q,tr[MAXN*20*20][2],mmin[MAXN*20*20],cnt=MAXN; //对CNT的初始化很重要 vector<int> divi[MAXN]; void Update(int cur,int x) { mmin[cur]=min(mmin[cur],x); for(int i=19;i>=0;i--) { bool d=(x&(1<<i)); if(!tr[cur][d]) tr[cur][d]=cnt++; cur=tr[cur][d];mmin[cur]=min(mmin[cur],x); } } int Query(int x,int cur,int lim) { if(mmin[cur]>lim) return -1; long long ret=0; for(int i=19;i>=0;i--) { bool d=(x&(1<<i));d=!d; if(mmin[tr[cur][d]]<=lim) //查看是否满足条件 cur=tr[cur][d],ret+=d*(1<<i); else cur=tr[cur][!d],ret+=(!d)*(1<<i); } return ret; } int main() { scanf("%d",&q); for(int i=0;i<MAXN*20*20;i++) mmin[i]=INF; for(int i=1;i<MAXN;i++) for(int j=i;j<MAXN;j+=i) divi[j].push_back(i); for(int i=1;i<=q;i++) { int op,x,k,s; scanf("%d",&op); if(op==1) { scanf("%d",&x); if(mark[x]) continue; mark[x]=true; for(int j=0;j<divi[x].size();j++) Update(divi[x][j],x); //将个数的所有倍数都加进Trie中 } else { scanf("%d %d %d",&x,&k,&s); if(x%k){puts("-1");continue;} printf("%d ",Query(x,k,s-x)); } } return 0; }
Review:
1、异或最值 <-------> 01Trie树
这两者之间的转换十分常用,需构建联系
Trie树的存储,一般使用Trie[MAXN][2]的方式更方便
其中01Trie树的MAXN要取大,要取到MAXN*20*20?(不确定,上界应该是MAXN*2^16?)
2、对于Trie树cnt初始化的问题
由于n棵Trie树已经有n个root,因此cnt应初始化为n+1!!!
3、当有时准确建图、建树要耗费太多时间时,考虑先全加进去,再通过极值初始化筛去不符合的点或边
ex:此处要想使得Trie树中X的倍数都来自于当前数列,不免会TLE
因此可以先将全部倍数都加进去,而不在数列的倍数min值初始化为INF即可