题目有n个操作,操作1代表往集合里面加入一个数字x,操作2会给出三个数字x,k,s,然后对于每一个2操作,题目有一个询问,要求从集合中找一个数vv满足下面的条件:
gcd(x,v)%k==0
x+v≤s
使x⊕v的值最大
题目让从现在的a集合中找出满足上面这三个条件的数。
首先我们可以想到第一个条件gcd(x,v)%k==0,那么必然有x%k==0和v%k==0
其次,我们很容易的想到01字典树的用法,我们利用字典树可以很容易的从中找出一堆数里面于一个数异或值最大的数(0找1,1找0),所以我们当k==1的时候gcd(x,v)%k==0这个条件一定满足,所以问题转化成了我们从集合中找出小于等于s-x范围内的的异或值最大的数,我们用minn[]数组标记字典树中当前节点插入进来的最小值,对于普通的01字典树求异或最大值稍作改变即可
对于k!=1的时候,因为v一定是k的倍数,所以我们可以在s−x范围内枚举k的倍数
然后每次在set[k]中二分找到大于s-x的第一个数,然后从大到小开始找,假设sum为当前x xor v的最大值,那么当当前的*it +x < sum,那么就直接break,原理是因为a ^ b <= a + b。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <set>
using namespace std;
int trie[10*100005][2];
int minn[10*100005];
int cnt;
set<int> s[100005];
void add(int x)
{
for(int i=1;i*i<=x;i++)
{
if(x%i==0)
s[i].insert(x),s[x/i].insert(x);
}
}
void insert(int x)
{
int rt=0;
minn[rt]=min(minn[rt],x);
for(int i=19;i>=0;i--)
{
int id=(x>>i)&1;
if(trie[rt][id]==0)
{
trie[rt][id]=cnt;
cnt++;
}
rt=trie[rt][id];
minn[rt]=min(x,minn[rt]);
}
}
int dfs(int tx,int ts)
{
int rt=0;
for(int i=19;i>=0;i--)
{
int id=(tx>>i)&1;
if(trie[rt][id^1]!=0 && minn[trie[rt][id^1]]+tx<=ts)
{
rt=trie[rt][id^1];
}
else if(trie[rt][id]!=0 && minn[trie[rt][id]]+tx<=ts)
{
rt=trie[rt][id];
}else
{
return -1;
}
//cout<<i<<":"<<tx<<":"<<minn[rt]<<endl;
}
return minn[rt];
}
int main() {
int q;
while(~scanf("%d",&q))
{
cnt=1;
memset(trie,0,sizeof(trie));
for(int i=1;i<=100000;i++)
s[i].clear();
for(int i=0;i<=1000000;i++)
minn[i]=1e9+7;
while(q--)
{
int t;
scanf("%d",&t);
if(t==1)
{
int u;
scanf("%d",&u);
add(u);insert(u);
}
else
{
int tx,tk,ts;
scanf("%d%d%d",&tx,&tk,&ts);
int ans=-1;
int anss=-1;
if(tx%tk!=0 || (tx+minn[0])>ts)
{
printf("%d\n",ans);
continue;
}
if(tk==1)
{
ans=dfs(tx,ts);
} else{
set<int>::iterator itr=s[tk].upper_bound(ts-tx);
if(itr!=s[tk].begin())
{
itr--;
for(;;itr--)
{
int v=*itr;
if(anss<(v^tx))
{
anss=v^tx;
ans=v;
}
if(tx+v<=anss)
break;
if(itr==s[tk].begin())
break;
}
}
}
printf("%d\n",ans);
}
}
}
return 0;
}