题面:
传送门
题目大意:
给定一个空集合,有两种操作:
一种是往集合中插入一个元素x,一种是给三个数x,k,s,问集合中是否存在v,使得gcd(x,v)%k==0,且x+v<=s若存在多个满足条件,则输出使得v⊕x最大的v。
分析:
首先,gcd(x,v)%k==0,由数论知识得该条件等价于x%k==0&&v%k==0
那么,我们怎么快速求出能整除k的v呢
对操作1输入的数x的每个因数,我们建立一个集合
s[i]存储能被i整除的所有x
且由于c++ STL的set的特性,集合内元素从小到大排列,我们可以快速求出x+v<=s的所有v,再从这些值中选出v⊕x最大的v即可
易错细节
1.在set中查找时我们要记得判断集合是否为空
2.注意upper_bound的返回值
3.集合中只有第一个数满足条件时的特判
因为我们是这样倒序遍历集合的 for(;it!=s[k].begin();it--)
所以当集合中只有第一个数满足条件时,it=s[k].begin(),会直接跳出循环
在循环结尾做一下特判就可以了
时间复杂度分析:
操作1时间复杂度
操作2时间复杂度
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<cmath>
#define maxn 100005
using namespace std;
set<int>s[maxn];
int n;
void div(int x){//分解因数,并将x插入每个因数对应的集合
int sq=(int)sqrt(x);
for(int i=1;i<=sq;i++){
if(x%i==0){
s[i].insert(x);
s[x/i].insert(x);
}
}
}
int get_ans(int x,int k,int maxs){
if(x%k!=0) return -1;
set<int>::iterator it;
if(s[k].empty()) return -1;//集合为空的特判
it=s[k].upper_bound(maxs-x);//查找x+v<=s的最大v (准确的说,是v的下标+1,因为upper_bound的返回值)
if(it==s[k].begin()) return -1;
it--;//由上知要-1
int ans=-1,sum=-1;
for(;it!=s[k].begin();it--){//从大到小找v⊕x最大的v
int v=*it;
if(sum>x+v) break;//因为v⊕x<=v+x
if(sum<(x^v)){
ans=v;
sum=x^v;
}
}
if(sum<(x^*it)) ans=*it;//只有第一个数满足条件时的特判
return ans;
}
int main(){
int cmd,x,k,s;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&cmd);
if(cmd==1){
scanf("%d",&x);
div(x);
}else{
scanf("%d %d %d",&x,&k,&s);
printf("%d
",get_ans(x,k,s));
}
}
}