CF 979D Kuro and GCD and XOR and SUM(异或 Trie)

CF 979D Kuro and GCD and XOR and SUM(异或 Trie)

给出q(<=1e5)个操作。操作分两种,一种是插入一个数u(<=1e5),另一种是给出三个数x,k,s(<=1e5),求当前所有u中满足,k|u,x+u<=s,且(xoplus u)最大的u。

做法好神啊。关于异或的问题有一种常见做法,就是利用01trie来查找在一堆数里面,哪个数与x的异或值最大。这道题就是这个思路。如果去掉k必须整除v这个条件,那么就转化成了上一个问题(只不过有最大值的限制,怎么解决具体看代码)。

这道题的做法非常神奇。我们建1e5个Trie,第i个Trie中插入值为i的倍数的数。这样,查询x,k,s时,只要查询第k个Trie即可,因为里面的数一定满足k|v。插入时要遍历u的所有因数si,然后将u插入第si个Trie。

注意,异或运算是在尾部对齐的,但是要在Trie上贪心,所以必须在插入和查询的数前补零,使他们长度相同。

分析一波复杂度:

  • 预处理:我们需要预处理出u的因数。u的最大值为Max=1e5。用类似筛法的方法,时间复杂度是(O(Max(1+frac{1}{2}+frac{1}{3}...+frac{1}{Max}))=O(MaxInMax))
  • 查询:就是Trie上的查询,总时间复杂度为(O(qlog_2Max))
  • 插入:一个数最多只有(log_2(Max))个因数((2*10^9)内因数最多的数之一是1837836000,有1536个因数),所以总的时间复杂度为(O(qlog^2_2(Max)))
  • 空间复杂度:最多插入(qlog_2(Max))个数,因此空间复杂度为(qlog_2^2(Max))

果然只能膜拜膜拜。

#include <cstdio> 
#include <vector>
using namespace std;

const int maxnum=1e5+5, maxq=1e5+5, maxn=maxq*17*17, INF=1e9;
//maxnum指插入的数的最大值  maxq指查询的最多数目 
//maxn指结点的最多数目(=maxq*插入几个trie*插入数的二进制长度)
int s[maxn][2], minm[maxn], v[maxn], tot;
int q, root[maxnum], use[maxnum];
vector<int> div[maxnum];

void init(){
	for (int i=1; i<maxnum; ++i)
		for (int j=i; j<maxnum; j+=i)
			div[j].push_back(i);
}

//把x插到对应的trie里,注意维护子树中的最小数  l:处理到从左到右第几位 
void insert(int &now, int x, int l){
	if (!now){ now=++tot; minm[now]=INF; }
	minm[now]=min(minm[now], x);
	if (l==-1){ v[now]+=x; return; }
	if ((x>>l)&1) insert(s[now][1], x, l-1);
	else insert(s[now][0], x, l-1);
}

//要找到v<=lim,并且x^v尽量大(贪心)。函数返回v 
//注意由于没有删除操作,路径底下一定有点。 
int query(int now, int x, int lim, int l){  //l:第几位 
	if (l==-1) return v[now];
	int s0=s[now][0], s1=s[now][1];
	if (!s0||minm[s0]>lim) return query(s[now][1], x, lim, l-1);
	if (!s1||minm[s1]>lim) return query(s[now][0], x, lim, l-1);
	if ((x>>l)&1) return query(s[now][0], x, lim, l-1);
	else return query(s[now][1], x, lim, l-1);
}

int main(){
	init();
	scanf("%d", &q); int op, x, k, s;
	for (int i=0; i<q; ++i){
		scanf("%d", &op);
		if (op==1){
			scanf("%d", &x); 
			if (use[x]) continue; use[x]=1;
			for (int j=0; j<div[x].size(); ++j)
				insert(root[div[x][j]], x, 18);
		} else {
			scanf("%d%d%d", &x, &k, &s);
			if (x%k||!minm[root[k]]||minm[root[k]]+x>s) puts("-1");  //注意可能没有一个数 
			else printf("%d
", query(root[k], x, s-x, 18));  //保证一定有解 
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/MyNameIsPc/p/9047757.html