P9809 [SHOI2006] 作业 Homework 浅显易懂讲解这道题为什么根号分治

题目:

我们有一堆数,找出模Y的最小值。

思路:

我们初步思考,会发现每个Y是一段,比如 1~Y , Y~2Y , 2Y~3Y ...

每个区间都可能有最小的答案。

这里对Y可以使用根号分治,因为:

当Y足够大时,每个区间都很大,区间数就很少。

而当Y足够小时,我们可以暴力这部分Y。

当Y足够大时,区间很大,我们对区间做处理:找大于1的最小值,大于Y的最小值,大于2Y的最小值,只需要找几次就能找完。

————

暂时规定Y小于V时,Y足够小。

A操作:

我们每插入一个数,挨着取模1~V并记录对应的最小值。(这里就是暴力,只记录1~V是可以承受的)

这样取模1~V的时候我们直接得到最小值。可以用map存。

(同时插入的数存入一个set中。)

B操作:

当模数小于V时,我们已经存好了,直接得到答案。

当模数大于V时,我们在每个区间找最小值。(因为区间很大,过几个区间可能就没有数了,结束就好了)

根号:

V怎么取呢?这。。

根号分治!

本题 Y ≤ 3e5,取个几百差不多,V*B == 3e5就行。(这个B对应大区间数目,大区间最小是V的时候也要覆盖整个Y,所以有个B)

可以取 : 480*625        近似根号了,因为我们区间长度是整数

代码细节:

可以直接看代码,也可以看这里帮助理解写法:

set存数,lower_bound是下界的意思(参考set::lower_bound - C++ Reference (cplusplus.com))

(可以测试lower_bound,当找的那个下界x不存在时,返回的是第一个比x大的数的迭代器

而up_bound找的就是第一个比x大的数的迭代器。这里我们要找最小的大于等于区间左边的数)

代码:


#define V 480
#define B 625

void solve()
{
	int n;
	cin >> n;
	sets;
	unordered_mapm;
	for (int i = 1; i <= n; i++)
	{
		char op;
		int val;
		cin >> op >> val;
		if (op == 'A')
		{
			s.insert(val);
			for (int i = 1; i < V; i++)
			{
				if (m.count(i))m[i] = min(m[i], val % i);
				else m[i] = val % i;
			}
		}
		else
		{
			int ans = LLONG_MAX;
			if (val >= V)
			{
				for (int i = 0; i <= B; i++)
				{
					auto it = s.lower_bound(i * val);
					if (it == s.end())
						break;//都比i*val大了
					ans = min(ans, *it - (i * val));
				}
			}
			else
			{
				ans = m[val];
			}
			cout << ans << endl;
		}

	}
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

然而。。。:

P9809 [SHOI2006] 作业 Homework 浅显易懂讲解这道题为什么根号分治_第1张图片

值域分块可以再优化,我不会。

我导师的讲解:

P9809 [SHOI2006] 作业 Homework 浅显易懂讲解这道题为什么根号分治_第2张图片

你可能感兴趣的:(算法,算法,根号分治)